我正在玩可组合的失败并设法用签名
编写一个函数getPerson :: IO (Maybe Person)
其中一个人是:
data Person = Person String Int deriving Show
它有效,我用以下的方式写了它:
import Control.Applicative
getPerson = do
name <- getLine -- step 1
age <- getInt -- step 2
return $ Just Person <*> Just name <*> age
,其中
getInt :: IO (Maybe Int)
getInt = do
n <- fmap reads getLine :: IO [(Int,String)]
case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
我编写此函数的目的是创建可组合的可能失败。虽然我对除了Maybe和IO之外的monad的经验很少,但是如果我有一个更复杂的数据类型以及更多的字段,那么链接计算并不复杂。
我的问题是如何在不用符号的情况下重写这个内容?由于我无法将值绑定到名称或年龄等名称,因此我不确定从哪里开始。
询问的原因只是为了提高我对(&gt;&gt; =)和(&lt; *&gt;)的理解,并构成失败和成功(不要用难以理解的单行代码捣乱我的代码)。
编辑:我想我应该澄清一下,“我应该怎样重写getPerson而不用do-notation”,我不关心getInt函数的一半。
答案 0 :(得分:20)
以这种方式将语法去掉(&gt;&gt; =)语法:
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 ))
在第一行之后的每一行中的每一行被转换为一个lambda,然后绑定到前一行。将值绑定到名称是一个完全机械化的过程。我不知道使用do-notation会不会影响可组合性;这完全是语法问题。
您的其他功能类似:
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
变为
Person <$> Just name <*> age
另外,你应该看看monad变形金刚。 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
。这被函数lift
提升到了MaybeT monad。
修改强>
newacct的评论表明我也应该提供一个模式匹配示例;它与一个重要的例外情况基本相同。考虑这个例子(列表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]
此处g
与f
完全相同,但如果模式匹配失败怎么办?
Prelude> f [Nothing]
[]
Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda
发生了什么事?这个特例是Haskell中一个最大的疣(IMO)的原因,Monad
类的fail
方法。在do-notation中,当模式匹配失败时,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
的用处取决于monad。对于列表,它非常有用,基本上使模式匹配在列表推导中起作用。它在Maybe
monad中也非常好,因为模式匹配错误会导致计算失败,这恰好是Maybe
应该Nothing
的时候。对于IO
,可能没那么多,因为它只是通过error
引发用户错误异常。
这是完整的故事。
答案 1 :(得分:4)
do
- 使用var <- e1; e2
表达>>=
表格的块,如下所示e1 >>= \var -> e2
。因此,您的getPerson
代码变为:
getPerson =
getLine >>= \name ->
getInt >>= \age ->
return $ Just Person <*> Just name <*> age
如您所见,这与使用do
的代码没有什么不同。
答案 2 :(得分:1)
实际上,根据this explaination,您的代码的确切翻译是
getPerson =
let f1 name =
let f2 age = return $ Just Person <*> Just name <*> age
f2 _ = fail "Invalid age"
in getInt >>= f2
f1 _ = fail "Invalid name"
in getLine >>= f1
getInt =
let f1 n = case n of
((x,""):[]) -> return (Just x)
_ -> return Nothing
f1 _ = fail "Invalid n"
in (fmap reads getLine :: IO [(Int,String)]) >>= f1
模式匹配示例
f x = do
Just n <- x
[n+1]
翻译为
f x =
let f1 Just n = [n+1]
f1 _ = fail "Not Just n"
in x >>= f1
显然,这个翻译结果的可读性低于lambda版本,但它可以使用或不使用模式匹配。