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

Dav*_*ave 11 monads haskell composition

我正在玩可组合的失败并设法用签名编写一个函数

getPerson :: IO (Maybe Person)
Run Code Online (Sandbox Code Playgroud)

一个人在哪里:

data Person = Person String Int deriving Show
Run Code Online (Sandbox Code Playgroud)

它的工作原理我用以下的方式编写了它:

import Control.Applicative

getPerson = do
    name <- getLine -- step 1
    age  <- getInt  -- step 2
    return $ Just Person <*> Just name <*> age 
Run Code Online (Sandbox Code Playgroud)

哪里

getInt :: IO (Maybe Int)
getInt = do
    n <- fmap reads getLine :: IO [(Int,String)]
    case n of
        ((x,""):[])   -> return (Just x)
        _ -> return Nothing
Run Code Online (Sandbox Code Playgroud)

我编写此函数的目的是创建可组合的可能失败.虽然我对除了Maybe和IO之外的monad的经验很少,但是如果我有一个更复杂的数据类型以及更多的字段,那么链接计算并不复杂.

我的问题是,如果没有写符号,我将如何重写?由于我无法将值绑定到名称或年龄等名称,因此我不确定从哪里开始.

提问的原因只是为了提高我对(>> =)和(<*>)的理解,并构成失败和成功(不要用难以辨认的单行代码来捣乱我的代码).

编辑:我想我应该澄清一下,"我应该怎么重写getPerson而不用do-notation",我不关心getInt函数的一半.

Joh*_*n L 20

以这种方式处理(>> =)语法的do-notation:

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 ))
Run Code Online (Sandbox Code Playgroud)

在第一个之后的每一行中的每一行被转换为一个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
Run Code Online (Sandbox Code Playgroud)

关于你似乎走向的方向的一些指示:

使用时Control.Applicative,<$>将纯函数提升到monad中通常很有用.在最后一行中有一个很好的机会:

Just Person <*> Just name <*> age
Run Code Online (Sandbox Code Playgroud)

Person <$> Just name <*> age
Run Code Online (Sandbox Code Playgroud)

此外,你应该看看monad变形金刚.该MTL包是最普遍的,因为它带有Haskell的平台,但也有其他的选择.Monad变换器允许您创建一个具有底层monad组合行为的新monad.在这种情况下,您正在使用具有该类型的函数IO (Maybe a).mtl(实际上是一个基础库,变换器)定义

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Run Code Online (Sandbox Code Playgroud)

这与您正在使用的类型相同,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
Run Code Online (Sandbox Code Playgroud)

getInt3除了MaybeT构造函数之外完全相同.基本上,只要你有一个m (Maybe a)你可以包装它MaybeT来创建一个MaybeT m a.这可以获得更简单的可组合性,正如您可以从新定义中看到的那样getPerson3.该功能根本不担心失败,因为它全部由MaybeT管道处理.剩下的一块是getLine,这只是一块IO String.这个函数被提升到了MaybeT monad中lift.

编辑 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]
Run Code Online (Sandbox Code Playgroud)

g与完全相同f,但如果模式匹配失败怎么办?

Prelude> f [Nothing]
[]

Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?这种特殊情况是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"
Run Code Online (Sandbox Code Playgroud)

现在我们有

Prelude> g' [Nothing]
[]
Run Code Online (Sandbox Code Playgroud)

fail有用性取决于monad.对于列表,它非常有用,基本上使模式匹配在列表推导中起作用.它在Maybemonad中也非常好,因为模式匹配错误会导致计算失败,这Maybe应该是应该的Nothing.对于IO,也许没有那么多,因为它只是抛出通过用户错误异常error.

这是完整的故事.

  • 一个非常小的评论:我会写`Person <$>只要将<*> age`命名为`人名<$>年龄` - 它更清楚地描述了哪里有效果(在'age`中)和哪里没有(在`人名`). (2认同)