组合 IO 操作

Jak*_*chs 3 haskell io-monad

我正在尝试编写一些 IO 包装的函数。

\n

我当前的代码(有效)是:

\n
getUserHome :: IO String\ngetUserHome = do\n    usr_id <- getRealUserID\n    homeDirectory <$> getUserEntryForID usr_id\n
Run Code Online (Sandbox Code Playgroud)\n

我正在寻找一种更方便的表示法(不使用 -do关键字的表示法)。

\n

没有任何 Monadic 沙拉我就写

\n
getUserHome = homeDirectory . getUserEntryForID . getRealUserID\n
Run Code Online (Sandbox Code Playgroud)\n

我猜想还有一个.尊重 Monad 的替代运算符..?但在我所有的寻找中,我还没有找到它。

\n
\n

我尝试过<$>,但这似乎不是我想要的:

\n
src/Main.hs:49:21: error:\n    \xe2\x80\xa2 Couldn\'t match type \xe2\x80\x98IO UserEntry\xe2\x80\x99 with \xe2\x80\x98UserEntry\xe2\x80\x99\n      Expected type: UserID -> UserEntry\n        Actual type: UserID -> IO UserEntry\n    \xe2\x80\xa2 In the second argument of \xe2\x80\x98(<$>)\xe2\x80\x99, namely \xe2\x80\x98getUserEntryForID\xe2\x80\x99\n      In the first argument of \xe2\x80\x98(<$>)\xe2\x80\x99, namely\n        \xe2\x80\x98homeDirectory <$> getUserEntryForID\xe2\x80\x99\n      In the expression:\n        homeDirectory <$> getUserEntryForID <$> getRealUserID\n   |\n49 |   homeDirectory <$> getUserEntryForID  <$> getRealUserID -- usr_id\n
Run Code Online (Sandbox Code Playgroud)\n

Wil*_*sem 8

你可以使用>>=,因为 Haskell 最终将do块“脱糖”为:

getUserHome :: IO String
getUserHome = getRealUserID >>= \usr_id -> homeDirectory <$> getUserEntryForID usr_id
Run Code Online (Sandbox Code Playgroud)

这可以简化为:

getUserHome :: IO String
getUserHome  = getRealUserID >>= fmap homeDirectory . getUserEntryForID
Run Code Online (Sandbox Code Playgroud)

或者:

getUserHome :: IO String
getUserHome  = getRealUserID >>= (homeDirectory <$>) . getUserEntryForID
Run Code Online (Sandbox Code Playgroud)


lef*_*out 6

首先始终考虑类型:

getRealUserID     ::              IO UserId
getUserEntryForID :: UserId    -> IO UserEntry
homeDirectory     :: UserEntry ->    String
Run Code Online (Sandbox Code Playgroud)

现在你可以:

  1. 适用getRealUserIDgetUserEntryForID. 这直接适合

    (>>=) :: m  a      -> (a      -> m  b        ) -> m  b
    (>>=) :: IO UserId -> (UserId -> IO UserEntry) -> IO UserEntry
    
    Run Code Online (Sandbox Code Playgroud)

    实际上,如果你希望它看起来像一个组合链,我更喜欢翻转版本,即

    getUserEntryForID =<< getRealUserID :: IO UserEntry
    
    Run Code Online (Sandbox Code Playgroud)
  2. 适用homeDirectory于此。在你的情况下,这根本不是一元的,所以你需要用fmapor来提升它<$>。考虑运算符优先级。

    fmap homeDirectory $ getUserEntryForID =<< getRealUserID
    homeDirectory <$> (getUserEntryForID =<< getRealUserID)
    
    Run Code Online (Sandbox Code Playgroud)

因为 monad 是关联的,所以你也可以反过来做:

  1. 撰写homeDirectorygetUserEntryForID. 后者已经是标准的Kleisli 箭头,您homeDirectory也可以提升到 Kleisli 以便使用 Kleisli 组合运算符:

    pure . homeDirectory :: UserEntry -> IO String
    pure . homeDirectory <=< getUserEntryForID :: UserId -> IO String
    
    Run Code Online (Sandbox Code Playgroud)
  2. 再次将整个事情应用到getRealUserID

    (pure . homeDirectory <=< getUserEntryForID) =<< getRealUserID
    
    Run Code Online (Sandbox Code Playgroud)

事实上,你也可以getRealUserID通过虚拟参数变成克莱斯利箭头()。这种风格在 Haskell 中相当罕见,但它的优点是 Kleisli 组合的结合性变得明显,就像普通函数组合的结合性一样:

pure . homeDirectory <=< getUserEntryForID <=< const getRealUserID $ ()
Run Code Online (Sandbox Code Playgroud)