Do 符号以这种方式将 (>>=) 语法脱糖:
getPerson = do
name <- getLine -- step 1
age <- getInt -- step 2
return $ Just Person <*> Just name <*> age
getPerson2 =
getLine >>=
( \name -> getInt >>=
( \age -> return $ Just Person <*> Just name <*> age ))
do 表示法中的每一行,在第一行之后,都被转换为 lambda,然后绑定到前一行。将值与名称绑定是一个完全机械的过程。我根本不明白使用或不使用 do 符号会如何影响可组合性;这严格来说是一个语法问题。
您的其他功能类似:
getInt :: IO (Maybe Int)
getInt = do
n <- fmap reads getLine :: IO [(Int,String)]
case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
getInt2 :: IO (Maybe Int)
getInt2 =
(fmap reads getLine :: IO [(Int,String)]) >>=
\n -> case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
关于您似乎前进的方向的一些指示:
使用时Control.Applicative
,使用它通常很有用<$>
将纯函数提升到 monad 中。最后一行有一个很好的机会:
Just Person <*> Just name <*> age
becomes
Person <$> Just name <*> age
另外,您应该研究 monad 转换器。这mtl http://hackage.haskell.org/package/mtl软件包最为广泛,因为它随 Haskell 平台一起提供,但还有其他选择。 Monad 转换器允许您通过底层 monad 的组合行为创建一个新的 monad。在本例中,您使用的函数类型为IO (Maybe a)
。 mtl(实际上是一个基础库,变压器)定义
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
这与您正在使用的类型相同,带有m
变量实例化于IO
。这意味着你可以写:
getPerson3 :: MaybeT IO Person
getPerson3 = Person <$> lift getLine <*> getInt3
getInt3 :: MaybeT IO Int
getInt3 = MaybeT $ do
n <- fmap reads getLine :: IO [(Int,String)]
case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
getInt3
完全相同,除了MaybeT
构造函数。基本上,任何时候你有一个m (Maybe a)
你可以把它包起来MaybeT
创建一个MaybeT m a
。这获得了更简单的可组合性,正如您可以通过新定义看到的那样getPerson3
。该函数根本不用担心失败,因为它全部由 MaybeT 管道处理。剩下的一块是getLine
,这只是一个IO String
。这通过函数提升到 MaybeT monad 中lift
.
Editnewacct 的评论建议我也应该提供一个模式匹配示例;除了一个重要的例外之外,它基本上是相同的。考虑这个例子(列表 monad 是我们感兴趣的 monad,Maybe
只是用于模式匹配):
f :: Num b => [Maybe b] -> [b]
f x = do
Just n <- x
[n+1]
-- first attempt at desugaring f
g :: Num b => [Maybe b] -> [b]
g x = x >>= \(Just n) -> [n+1]
Here g
做完全相同的事情f
,但是如果模式匹配失败怎么办?
Prelude> f [Nothing]
[]
Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda
这是怎么回事?这个特殊情况是 Haskell 中最大的缺点之一(IMO)的原因,Monad
班级的fail
方法。在 do 表示法中,当模式匹配失败时fail
叫做。实际的翻译会更接近:
g' :: Num b => [Maybe b] -> [b]
g' x = x >>= \x' -> case x' of
Just n -> [n+1]
_ -> fail "pattern match exception"
现在我们有
Prelude> g' [Nothing]
[]
fail
s 的用处取决于 monad。对于列表来说,它非常有用,基本上使模式匹配在列表理解中起作用。在这方面也是非常不错的Maybe
monad,因为模式匹配错误会导致计算失败,这正是当Maybe
应该Nothing
. For IO
,也许没有那么多,因为它只是通过抛出用户错误异常error
.
这就是完整的故事。