14

在阅读有关 monad 的文章时,我不断看到诸如“Xyz monad 中的计算”之类的短语。计算“在”某个单子中意味着什么?

我认为我对 monad 的含义有相当的了解:允许计算产生通常是某种预期类型的​​输出,但可以替代或附加地传达一些其他信息,例如错误状态、日志信息、状态等,以及允许将此类计算链接起来。

但我不明白如何将计算称为“在”单子中。这只是指产生一元结果的函数吗?

示例:(搜索“计算”)

4

5 回答 5

12

这只是指产生一元结果的函数吗?

是的,简而言之。


总而言之,这是因为Monad允许您将值注入其中(通过return),但一旦进入Monad它们就会卡住。您必须使用某些功能,例如evalWriterrunContMonad将值“输出”更严格。

不仅如此,Monad(实际上,它的合作伙伴,Applicative)是拥有“容器”并允许在其中进行计算的本质。这就是(>>=)让您能够在“内部”进行有趣计算的能力Monad

因此,诸如Monad m => m a -> (a -> m b) -> m b让您在Monad. 像Monad m => a -> m a让你注入到Monad. 像这样的函数m a -> a可以让你“逃脱”,Monad除非它们通常不存在(仅在特定情况下)。因此,为了对话,我们喜欢讨论具有结果类型的函数,例如Monad m => m a“在 monad 内部”。

于 2013-02-22T04:52:17.643 回答
12

通常,“单子中的计算”不仅意味着返回单子结果的函数,还意味着在do块内使用的此类函数,或作为 的第二个参数的一部分(>>=),或其他任何等效的东西。区别与您在评论中所说的有关:

“计算”发生在 func f 中,在从输入 monad 中提取 val 之后,在结果被包装为 monad 之前。我看不出计算本身是如何“在”单子中的。它似乎明显地“脱离”了单子。

这不是一个糟糕的思考方式——事实上,do符号鼓励它,因为它是一种方便的看待事物的方式——但它确实会导致一种略微误导的直觉。没有任何东西可以从单子中“提取”出来。要了解原因,请忘记-- 它是一种支持符号(>>=)的复合操作。domonad 更基本的定义是三个正交函数:

fmap :: (a -> b) -> (m a -> m b)
return :: a -> m a
join :: m (m a) -> m a

...m单子在哪里。

现在考虑如何(>>=)用这些来实现:从类型m a和的参数开始a -> m b,你唯一的选择是使用fmap来获取类型的东西m (m b),之后你可以使用join扁平化嵌套的“层”来获取只是m b

换句话说,没有任何东西被从 monad 中“取出”——相反,将计算视为更深入monad,连续的步骤被折叠成 monad 的单层。

请注意,从这个角度来看,monad 定律也简单得多——基本上,他们说,join只要保留嵌套顺序(一种关联性)并且引入的 monad 层return什么都不做(的标识值join)。

于 2013-02-22T15:04:18.383 回答
4

通常,当以“类似集合”的单子为例时,单子的东西更容易掌握。想象一下,您计算两点的距离:

data Point = Point Double Double

distance :: Point -> Point -> Double
distance p1 p2 = undefined

现在你可能有一定的背景。例如,其中一个点可能是“非法的”,因为它超出了某些范围(例如在屏幕上)。因此,您将现有的计算包装在Maybemonad 中:

distance :: Maybe Point -> Maybe Point -> Maybe Double
distance p1 p2 = undefined

您具有完全相同的计算,但具有可能“无结果”(编码为Nothing)的附加功能。

或者您有两组“可能”点,并且需要它们的相互距离(例如,稍后使用最短连接)。那么 list monad 就是你的“上下文”:

distance :: [Point] -> [Point] -> [Double]
distance p1 p2 = undefined

或者这些点是由用户输入的,这使得计算“不确定”(从某种意义上说,你依赖于外部世界中的事物,这可能会改变),那么IOmonad 就是你的朋友:

distance :: IO Point -> IO Point -> IO Double
distance p1 p2 = undefined

计算始终保持不变,但恰好发生在某个“上下文”中,这增加了一些有用的方面(失败、多值、不确定性)。您甚至可以组合这些上下文(monad 转换器)。

您可以编写一个统一上述定义的定义,并适用于任何monad:

 distance :: Monad m => m Point -> m Point -> m Double
 distance p1 p2 = do
     Point x1 y1 <- p1
     Point x2 y2 <- p2
     return $ sqrt ((x1-x2)^2 + (y1-y2)^2)  

这证明了我们的计算确实独立于实际的 monad,这导致了“x 在 (-side) y monad 中计算”的公式。

于 2013-02-22T07:46:40.737 回答
2

查看您提供的链接,“计算”的常见用法似乎与单个单子值有关。摘录:

简单介绍——这里我们在 SM monad 中运行计算,但计算是 monadic 值:

-- run a computation in the SM monad
runSM                   :: S -> SM a -> (a,S)

所有关于 monads -先前的计算是指序列中的 monadic 值:

>> 函数是一个便利运算符,用于绑定不需要序列中先前计算的输入的一元计算

理解 monads - 这里的第一个计算可以参考例如getLine一个 monadic 值:

(绑定)给出了在另一个计算中使用计算结果的内在想法,而不需要运行计算的概念。

所以打个比方,如果我说i = 4 + 2,那i是价值6,但它同样是一个计算,即计算4 + 2。似乎链接的页面在这个意义上使用计算- 计算作为单子值 - 至少在某些时候,在这种情况下,使用给定单子中的“计算”表达式是有意义的。

于 2013-02-22T11:51:42.643 回答
1

考虑IO单子。类型值IO a是对大量(通常是无限)行为的描述,其中行为是一系列 IO 事件(读取、写入等)。这样的值称为“计算”;在这种情况下,它是IOmonad 中的计算。

于 2013-02-22T03:50:59.830 回答