首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在不使用Do符号的情况下编写代码

如何在不使用Do符号的情况下编写代码
EN

Stack Overflow用户
提问于 2011-08-29 19:33:30
回答 3查看 2.1K关注 0票数 11

我一直在处理可组合的失败,并设法编写了一个带有签名的函数

代码语言:javascript
复制
getPerson :: IO (Maybe Person)

其中一个人是:

代码语言:javascript
复制
data Person = Person String Int deriving Show

它可以工作,我用do-notation编写了如下代码:

代码语言:javascript
复制
import Control.Applicative

getPerson = do
    name <- getLine -- step 1
    age  <- getInt  -- step 2
    return $ Just Person <*> Just name <*> age 

哪里

代码语言:javascript
复制
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函数的一半。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-08-29 19:55:58

Do-notation desugars to (>>=)语法如下:

代码语言:javascript
复制
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根本不会影响可组合性;这只是一个严格的语法问题。

您的其他函数与此类似:

代码语言:javascript
复制
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中通常很有用。在最后一行中有一个很好的机会:

代码语言:javascript
复制
Just Person <*> Just name <*> age

变成了

代码语言:javascript
复制
Person <$> Just name <*> age

此外,您还应该研究一下monad transformers。mtl包是最广泛使用的,因为它随Haskell平台一起提供,但也有其他选择。Monad转换器允许您创建具有底层monad组合行为的新monad。在本例中,您将使用类型为IO (Maybe a)的函数。mtl (实际上是一个基库,transformers)定义

代码语言:javascript
复制
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

这与您正在使用的类型相同,只是在IO处实例化了m变量。这意味着你可以这样写:

代码语言:javascript
复制
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只是用于模式匹配):

代码语言:javascript
复制
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完全相同的操作,但是如果模式匹配失败怎么办?

代码语言:javascript
复制
Prelude> f [Nothing]
[]

Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda

到底怎么回事?这种特殊的情况是Haskell中最大的缺点之一(IMO)的原因,即Monad类的fail方法。在do-notation中,当模式匹配失败时,将调用fail。实际的翻译应该更接近于:

代码语言:javascript
复制
g' :: Num b => [Maybe b] -> [b]
g' x = x >>= \x' -> case x' of
                      Just n -> [n+1]
                      _      -> fail "pattern match exception"

现在我们有了

代码语言:javascript
复制
Prelude> g' [Nothing]
[]

fail的有用性取决于monad。对于列表,它非常有用,基本上可以在列表理解中实现模式匹配。这在Maybe monad中也很好用,因为模式匹配错误会导致计算失败,而这正是Maybe应该为Nothing的时候。对于IO,可能不是很多,因为它只是通过error抛出了一个用户错误异常。

这就是完整的故事。

票数 22
EN

Stack Overflow用户

发布于 2011-08-29 19:56:34

do-blocks的形式是var <- e1; e2 do-blocks to expression using >>=,如下e1 >>= \var -> e2所示。所以你的getPerson代码变成了:

代码语言:javascript
复制
getPerson =
    getLine >>= \name ->
    getInt  >>= \age ->
    return $ Just Person <*> Just name <*> age

如您所见,这与使用do的代码没有太大不同。

票数 4
EN

Stack Overflow用户

发布于 2013-07-27 21:49:39

实际上,根据this explaination的说法,代码的确切翻译是

代码语言:javascript
复制
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

和模式匹配示例

代码语言:javascript
复制
f x = do
  Just n <- x
  [n+1]

翻译为

代码语言:javascript
复制
f x =
  let f1 Just n = [n+1]
      f1 _ = fail "Not Just n"
  in x >>= f1

显然,这个翻译后的结果比lambda版本的可读性差,但它可以在有或没有模式匹配的情况下工作。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7229518

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档