我一直在处理可组合的失败,并设法编写了一个带有签名的函数
getPerson :: IO (Maybe Person)其中一个人是:
data Person = Person String Int deriving Show它可以工作,我用do-notation编写了如下代码:
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我写这个函数的目的是创建可组合的可能失败。虽然除了可能和IO之外,我几乎没有使用monads的经验,但如果我有一个包含更多字段的更复杂的数据类型,那么链接计算就不会很复杂。
我的问题是,在没有do-notation的情况下,我该如何重写?因为我不能将值绑定到name或age之类的名称,所以我不确定从哪里开始。
问这个问题的原因只是为了提高我对(>>=)和(<*>)的理解,以及编写失败和成功的代码(而不是用晦涩的一行程序来迷惑我的代码)。
编辑:我想我应该澄清一下,“我应该如何在没有do-notation的情况下重写getPerson”,我并不关心getInt函数的一半。
发布于 2011-08-29 19:55:58
Do-notation desugars to (>>=)语法如下:
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-notation中的每一行都被转换为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 transformers。mtl包是最广泛使用的,因为它随Haskell平台一起提供,但也有其他选择。Monad转换器允许您创建具有底层monad组合行为的新monad。在本例中,您将使用类型为IO (Maybe a)的函数。mtl (实际上是一个基库,transformers)定义
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }这与您正在使用的类型相同,只是在IO处实例化了m变量。这意味着你可以这样写:
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除了MaybeT构造函数之外,getInt3完全相同。基本上,只要您有一个m (Maybe a),就可以将它包装在MaybeT中来创建MaybeT m a。这获得了更简单的可组合性,正如您可以从getPerson3的新定义中看到的那样。该函数根本不担心失败,因为它完全由MaybeT管道处理。剩下的一部分是getLine,它只是一个IO String。这被lift函数提升到MaybeT monad中。
编辑newacct的评论建议我也应该提供一个模式匹配示例;除了一个重要的例外,它基本上是相同的。考虑这个例子( list 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抛出了一个用户错误异常。
这就是完整的故事。
发布于 2011-08-29 19:56:34
do-blocks的形式是var <- e1; e2 do-blocks to expression using >>=,如下e1 >>= \var -> e2所示。所以你的getPerson代码变成了:
getPerson =
getLine >>= \name ->
getInt >>= \age ->
return $ Just Person <*> Just name <*> age如您所见,这与使用do的代码没有太大不同。
发布于 2013-07-27 21:49:39
实际上,根据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版本的可读性差,但它可以在有或没有模式匹配的情况下工作。
https://stackoverflow.com/questions/7229518
复制相似问题