首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Monad `fail`与MonadPlus `mzero`的正确用法

Monad `fail`与MonadPlus `mzero`的正确用法
EN

Stack Overflow用户
提问于 2011-02-17 08:50:06
回答 2查看 2.6K关注 0票数 22

这是一个在设计代码中多次出现的问题,尤其是在库中。它里面似乎有some interest,所以我想它可能会成为一个很好的社区维基。

Monad中的fail方法被一些人认为是一个瘤;这是对类的一个有点武断的添加,而不是来自原始的范畴理论。当然,在当前的情况下,许多Monad类型都有逻辑和有用的fail实例。

MonadPlus类是monad的一个子类,它提供了一个mzero方法,该方法在逻辑上将失败的概念封装在Monad中。

因此,想要编写一些执行某种故障处理的一元代码的库设计人员可以选择让他的代码在Monad中使用fail方法,或者将他的代码限制在MonadPlus类中,这样他就可以很好地使用mzero,即使他根本不关心一元组合mplus操作。

在这个关于proposals to reform the MonadPlus class.的维基页面上有一些关于这个主题的讨论

所以我想我有一个特别的问题:

哪些monad实例(如果有)具有自然的fail方法,但不能是MonadPlus的实例,因为它们没有mplus__的逻辑实现?

但我最感兴趣的是关于这个话题的讨论。谢谢!

编辑:我想到了最后一个想法。我最近了解到(尽管它就在fail的文档中),一元的"do“表示法是以这样一种方式去糖化的,即模式匹配失败,就像在(x:xs) <- return []中调用单数的fail

看起来,语言设计者在Monad中包含fail时,一定受到了内置到haskell语法中的一些自动故障处理的强烈影响。

EN

回答 2

Stack Overflow用户

发布于 2011-02-17 21:29:27

想想Either吧。它的一元实例如下所示:

代码语言:javascript
复制
{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
  (Left x) >>= _   = Left x
  (Right a)  >>= f = f a
  return           = Right
  fail             = Left

(为了允许像Either String这样的实例,我们需要FlexibleInstances )

因此,它基本上就像Maybe一样,如果发生了什么事情,它会提供一个可选的错误消息。您不能使用mzero重新创建它,因为您不能将错误消息添加到失败中。它与fail略有不同。

每个mplus实例都应该满足以下两个定律:

代码语言:javascript
复制
mzero `mplus` a -> a
a `mplus` mzero -> a

很简单,不是吗?但这些法律让mplus变得特别。有了它们,就可以为它编写一个合理的MonadPlus实例:

代码语言:javascript
复制
instance MonadPlus (Either a) where
  mzero = Left undefined
  mplus (Left _) b = b
  mplus a _        = a

这是什么?它代表了一种选择。如果第一次计算成功,则返回。否则,mplus将返回第二次计算。请注意它与(>>)的不同之处,后者不满足定律:

代码语言:javascript
复制
Left a   >>    Right b -> Left a
Left a `mplus` Right b -> Right b

(>>)将在第一次计算时停止,而mplus将尝试第二次计算。[]的行为也是这样的:

代码语言:javascript
复制
[] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]

这仅仅是讨论MonadPlus的一些方面,特别是mplus相对于(>>)的一些方面。

票数 11
EN

Stack Overflow用户

发布于 2011-02-17 23:43:12

在这个回答中,我想讨论这个话题,为什么failMonad的成员。我不想将它添加到我的other answer中,因为它涵盖了另一个主题。

尽管单子的数学定义不包含fail,但Haskell98的创建者将其放入了Monad类型类中。为什么?

为了简化monads的使用并使其更容易抽象monads的用法,他们引入了do表示法,这是一块非常有用的糖。例如,下面的代码:

代码语言:javascript
复制
do putStr "What's your full name? "
   [name,surname] <- getLine >>= return . words
   putStr "How old are you? "
   age <- getLine >>= return . read
   if age >= 18
      then putStrLn $ "Hello Mr / Ms " ++ surname
      else putStrLn $ "Hello " ++ name

翻译为:

代码语言:javascript
复制
putStr "What's your full name? " >>
getLine >>= return . words >>= \[name,surname] ->
putSr "How old are you? " >>
getLine >>= return . read >>= \age ->
if age >= 18
   then putStrLn $ "Hello Mr / Ms " ++ surname
   else putStrLn $ "Hello " ++ name

这里有什么问题?想象一下,你有一个名字,中间有一个空格,比如Jon M. Doe。在本例中,整个结构将是_|_。当然,您可以通过使用let添加一些即席功能来解决此问题,但这纯粹是样板。在创建Haskell 98的时候,还没有像今天这样的异常系统,您可以简单地捕获失败的模式匹配。此外,不完整的模式被认为是糟糕的编码风格。

解决方案是什么?Haskell98的创建者添加了一个特殊的函数fail,在不匹配的情况下调用。脱脂效果看起来有点像这样:

代码语言:javascript
复制
putStr "What's your full name? " >> let
  helper1 [name,surname] =
    putSr "How old are you? " >> let
      helper2 age =
        if age >= 18
           then putStrLn $ "Hello Mr / Ms " ++ surname
           else putStrLn $ "Hello " ++ name
      helper2 _ = fail "..."
    in getLine >>= return . read >>= helper2
  helper1 _ = fail "..."
in getLine >>= return . words >>= helper1

(我不确定是否真的有helper2,但我想是的)

如果你再看一遍,你就会发现它是多么的聪明。首先,永远不会有不完整的模式匹配,其次,您可以使fail可配置。为了实现这一点,他们只需将fail放入monads定义中。例如,对于Maybefail就是Nothing,而对于Either String实例,它就是Left。通过这种方式,可以很容易地编写独立于monad的一元代码。例如,在很长一段时间内,lookup被定义为(Eq a,Monad b) => a -> [(a, b)] -> m b,如果不匹配,lookup将返回fail

现在,在Haskell社区中仍然有一个大问题:向fail这样的Monad类型类添加一些完全独立的东西是不是一个坏主意?我不能回答这个问题,但我认为这个决定是正确的,因为其他地方对fail来说并不那么舒服。

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

https://stackoverflow.com/questions/5023969

复制
相关文章

相似问题

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