如何在没有Do符号的情况下书写

时间:2011-08-29 11:33:30

标签: haskell monads composition

我正在玩可组合的失败并设法用签名

编写一个函数
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函数的一半。

3 个答案:

答案 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]

此处gf完全相同,但如果模式匹配失败怎么办?

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版本,但它可以使用或不使用模式匹配。