不能 return 可能导致 IO Monad

Can not return maybe result in the IO Monad

我不明白为什么这个代码示例不起作用,但在 RWH 书中它起作用了:

module Monads where 
    import Data.Maybe
    import Control.Monad
    amap=[("a",1),("bb",2)]
    bmap=[(1,100),(2,200)]
    final=[(100,1000),(200,2000)]

    f::String->IO (Maybe Int)
    f par=do
          a<-lookup par amap
          b<-lookup a bmap
          lookup b final

它不起作用,我不明白它是如何工作的,因为最后一行 returns 是 Maybe something 而不是 IO something.

我也试过将最后一行更改为:
return (lookup b final)
根据我的推理应该是完美的(查找 returns a Maybe Int 然后用 return 包装它)

我在使用 return

时出现以下错误
* Couldn't match type `Maybe' with `IO'
      Expected type: IO Integer
        Actual type: Maybe Integer
    * In a stmt of a 'do' block: b <- lookup a bmap
      In the expression:
        do a <- lookup par amap
           b <- lookup a bmap
           return (lookup b final)
      In an equation for `f':
          f par
            = do a <- lookup par amap
                 b <- lookup a bmap
                 return (lookup b final)
   |
11 |           b<-lookup a bmap
   |              ^^^^^^^^^^^^^

对于给定的类型签名,您需要将最终调用 lookup 的值 return 提升为 IO 值。

f::String->IO (Maybe Int)
f par = return $ do
      a <- lookup par amap
      b <- lookup a bmap
      lookup b final

do 表达式是使用 >>= 的语法糖,它在 Maybe monad 中运行,在本例中不是 IO,而是 return 获取结果 Maybe Int 值并生成所需的 IO (Maybe Int) 值。它可以脱糖为

f par = return (lookup par amap >>= \a -> lookup a bmap >>= \b -> lookup b final)

但是,除了强制您使用 return 的类型签名之外,f 没有其他 要求 到 return 一个 IO 值;它的参数和三个关联列表中的任何一个都不以任何方式涉及 IO,因此您可以简单地更改类型签名以消除对 IO 的任何引用并纯粹在 Maybe monad 中操作。

f :: String -> Maybe Int
f par = do
    a <- lookup par amap
    b <- lookup a bmap
    lookup b final

旁白:摆脱 do

由于您将对 lookup 的各种调用链接在一起,如果您能以无点样式将每个结果提供给下一个调用,那就太好了。为此,您需要颠倒 lookup 接受其参数的顺序。使用 flip 很简单:flip lookup :: [(a,b)] -> a -> Maybe b.

f :: String -> Maybe Int
f par = let lookup' = flip lookup
        in lookup' amap par >>= lookup' bmap >>= lookup' final

更进一步,您可以使用从 Control.Monad 导入的 >=> 运算符完全删除对 par 的引用。将其类型与 >>=:

的类型进行比较
:t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
:t (>=>)
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c

不是以 Maybe 值开始,而是 编写 lookup' 的调用,并将起始字符串提供给函数。

f :: String -> Maybe Int
f = let lookup' = flip lookup
     in lookup' amap >=> lookup' bmap >=> lookup' final