Monads - 他们在哪里必要?

时间:2012-08-23 14:12:22

标签: language-agnostic functional-programming monads

前几天我在谈论函数式编程 - 特别是Haskell和一些Java / Scala人,他们问我Monad是什么,他们在哪里需要。

好的定义和例子并不那么难 - Maybe MonadIO MonadState Monad等,所以每个人都至少部分地对我说Monads是件好事

但Monad必须在哪里 - Maybe可以通过-1设置中的Integer等神奇值,或""设置中的String来避免State }。我写了一个没有-1 Monad的游戏,这个游戏并不好,但是初学者就是这么做的。

所以我的问题: Monads需要在哪里? - 根本无法避免。 (并没有混淆 - 我喜欢Monads并使用它们,我只是想知道)。

修改

我想我必须澄清我认为使用“Magic Values”是一个很好的解决方案,但很多程序员都会使用它们,尤其是在低级语言中 C < / strong>或者在SHell中通过返回IO来暗示错误。

我已经清楚,不使用monad不是一个好主意。抽象通常非常有用,但也很复杂,因此很多人都在讨论monad的概念。

我的问题的核心是,如果有可能做例如{{1}},没有monad并且仍然是纯粹的和功能性的。我知道将一个已知的好解决方案放在一边,以及使用燧石和火种点燃火灾而不是使用打火机将是乏味和痛苦的。

文章@Antal S-Z指的是伟大的you could have invented monads,我撇去它,当我有更多时间时肯定会读它。 @Antal S-Z i remember the time before monads提到的博客文章评论中隐藏着更具启发性的答案,这是我在问这个问题时所寻找的东西。

3 个答案:

答案 0 :(得分:12)

我不认为你需要 monad。当你使用某些功能时,它们只是一种自然显现的模式。我见过这种观点的最佳解释是Dan Piponi(sigfpe)的优秀博客文章"You Could Have Invented Monads! (And Maybe You Already Have.)",这个答案的灵感来源于此。

你说你在没有使用州monad的情况下写了一个游戏。它看起来像什么?很有可能你最终使用类似openChest :: Player -> Location -> (Item,Player)类型的函数(打开一个箱子,可能会损坏玩家的陷阱,并返回找到的项目)。一旦您需要将它们组合起来,您可以手动(let (item,player') = openChest player loc ; (x,player'') = func2 player' y in ...)或重新实现状态monad的>>=运算符。

或者假设我们使用哈希映射/关联数组的语言,我们不使用monad。我们需要查找一些项目并与之合作;也许我们正试图在两个用户之间发送消息。

send username1 username2 = {
    user1 = users[username1]
    user2 = users[username2]
    sendMessage user1 user2 messageBody
}

但等等,这不行; username1username2可能会丢失,在这种情况下,它们将是nil-1或其他内容,而不是所需的值。或者在关联数组中查找键可以返回类型Maybe a的值,因此这甚至会是类型错误。相反,我们必须编写类似

的内容
send username1 username2 = {
    user1 = users[username1]
    if (user1 == nil) return
    user2 = users[username2]
    if (user2 == nil) return
    sendMessage user1 user2 messageBody
}

或者,使用Maybe

send username1 username2 =
    case users[username1] of
      Just user1 -> case users[username2] of
                      Just user2 -> Just $ sendMessage user1 user2 messageBody
                      Nothing    -> Nothing
      Nothing    -> Nothing

伊克!这是混乱和过度嵌套。因此,我们定义了某种功能,它结合了可能失败的动作。也许像是

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= Just x  = f x
f >>= Nothing = Nothing

所以你可以写

send username1 username2 =
  users[username1] >>= $ \user1 ->
  users[username2] >>= $ \user2 ->
  Just (sendMessage user1 user2 messageBody)

如果你真的不想使用Maybe,那么你可以实现

f >>= x = if x == nil then nil else f x

同样的原则适用。

但是,我真的建议阅读"You Could Have Invented Monads!"这是我对monad有这种直觉的地方,并且更好更详细地解释它。 Monad在使用某些类型时会自然出现。有时候你会明确地表达这种结构,有时你却不这样做,但仅仅因为你没有采取这种结构并不意味着它不存在。您永远不需要使用monad,因为您不需要使用特定的结构,但通常这是很自然的事情。在许多其他方面,识别共同模式可以让你编写一些非常通用的代码。

(另外,正如我用过的第二个例子所示,请注意你已经用魔法值取代Maybe将宝宝扔出洗澡水。仅仅因为Maybe是一个monad而不是意味着你必须像一个一样使用它;列表也是monad,函数(形式为r ->)也是如此,但你不建议删除它们!: - ))

答案 1 :(得分:4)

我可以使用“哪里是 X 这个必要且不可避免的短语?”其中 X 是计算中的任何东西;那会是什么意思?

相反,我认为更有价值的是,“ X 提供什么价值提供?”“。

最基本的答案是计算中的大多数 X 提供了一个有用的抽象,使得将代码放在一起变得更容易,更乏味,更不容易出错。

好的,但你不需要抽象,对吧?我的意思是,我可以手工输入一些代码做同样的事情,对吧?是的,当然,它只是一堆0和1,所以让我们看看谁可以更快地编写XML解析器,我使用Java / Haskell / C或者使用图灵机。


Re monads:因为monad通常处理effectful computations,所以这种抽象在组合有效函数时最有用。

我对你的“魔法价值也许monad”提出异议。这种方法为程序员提供了一种非常不同的抽象,并且比实际 Maybe monad更不安全,更乏味,更容易处理。此外,阅读这些代码,程序员的意图将不那么明确。换句话说,它忽略了真正的monad的全部意义,即提供抽象。

我还要注意monad不是Haskell的基础:

  1. do - 符号只是语法糖,可以完全替换为>>=>>而不会失去任何表现力

  2. 他们(以及他们的组合器,例如join>>=mapM等)可以在 Haskell中编写

  3. 它们可以用任何支持高阶函数的语言编写,甚至可以用Java编写。因此,如果您必须使用没有monad的Lisp,您可以自己在Lisp中实现它们而不会有太多麻烦

答案 2 :(得分:0)

因为monad类型返回相同类型的答案,所以monad类型的实现可以强制执行&amp;保留语义。然后,在您的代码中,您可以使用该类型链接操作&amp;让它强制执行其规则,无论其包含哪种类型。

例如,Java 8中的Optional类强制执行包含的值存在的规则&amp;非空的,或者不存在。只要您使用Optional类,无论是否使用flatMap,您都要围绕包含的数据类型包装该规则。没有人可以作弊或遗忘,并使用present = true添加值= null。

因此在代码之外声明-1将是一个哨兵值并且意味着这样 - 并且很好,但是你仍然依赖于你自己以及在代码中工作以尊重该语义的其他人。如果一个新人加入并开始使用-1000000来表示相同的事情,那么语义需要在代码之外强制执行(可能使用引导管?)而不是通过代码机制。

因此,您不必在程序中一致地应用某些规则,而是可以信任monad来保留该规则(或其他语义) - 而不是任意类型。

通过这种方式,您可以通过在它们周围包装语义来扩展类型的功能,而不是将“isPresent”添加到代码库中的每个类型。

众多monadic实用程序类型的存在表明这种使用语义包装类型的机制是一个非常有用的技巧。如果您有自己想要添加的语义,可以使用monad模式编写自己的类,然后将字符串或浮点数或整数或其他内容注入其中。

但简单的回答是monad是一种很好的方法,可以在流畅或可链接的容器中包含常见类型,以添加规则和用法,而无需对底层类型的实现进行大惊小怪。