在阅读有关 monad 的文章时,我不断看到诸如“Xyz monad 中的计算”之类的短语。计算“在”某个单子中意味着什么?
我认为我对 monad 的含义有相当的了解:允许计算产生通常是某种预期类型的输出,但可以替代或附加地传达一些其他信息,例如错误状态、日志信息、状态等,以及允许将此类计算链接起来。
但我不明白如何将计算称为“在”单子中。这只是指产生一元结果的函数吗?
示例:(搜索“计算”)
在阅读有关 monad 的文章时,我不断看到诸如“Xyz monad 中的计算”之类的短语。计算“在”某个单子中意味着什么?
我认为我对 monad 的含义有相当的了解:允许计算产生通常是某种预期类型的输出,但可以替代或附加地传达一些其他信息,例如错误状态、日志信息、状态等,以及允许将此类计算链接起来。
但我不明白如何将计算称为“在”单子中。这只是指产生一元结果的函数吗?
示例:(搜索“计算”)
这只是指产生一元结果的函数吗?
是的,简而言之。
总而言之,这是因为Monad
允许您将值注入其中(通过return
),但一旦进入Monad
它们就会卡住。您必须使用某些功能,例如evalWriter
或runCont
比Monad
将值“输出”更严格。
不仅如此,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 内部”。
通常,“单子中的计算”不仅意味着返回单子结果的函数,还意味着在do
块内使用的此类函数,或作为 的第二个参数的一部分(>>=)
,或其他任何等效的东西。区别与您在评论中所说的有关:
“计算”发生在 func f 中,在从输入 monad 中提取 val 之后,在结果被包装为 monad 之前。我看不出计算本身是如何“在”单子中的。它似乎明显地“脱离”了单子。
这不是一个糟糕的思考方式——事实上,do
符号鼓励它,因为它是一种方便的看待事物的方式——但它确实会导致一种略微误导的直觉。没有任何东西可以从单子中“提取”出来。要了解原因,请忘记-- 它是一种支持符号(>>=)
的复合操作。do
monad 更基本的定义是三个正交函数:
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
)。
通常,当以“类似集合”的单子为例时,单子的东西更容易掌握。想象一下,您计算两点的距离:
data Point = Point Double Double
distance :: Point -> Point -> Double
distance p1 p2 = undefined
现在你可能有一定的背景。例如,其中一个点可能是“非法的”,因为它超出了某些范围(例如在屏幕上)。因此,您将现有的计算包装在Maybe
monad 中:
distance :: Maybe Point -> Maybe Point -> Maybe Double
distance p1 p2 = undefined
您具有完全相同的计算,但具有可能“无结果”(编码为Nothing
)的附加功能。
或者您有两组“可能”点,并且需要它们的相互距离(例如,稍后使用最短连接)。那么 list monad 就是你的“上下文”:
distance :: [Point] -> [Point] -> [Double]
distance p1 p2 = undefined
或者这些点是由用户输入的,这使得计算“不确定”(从某种意义上说,你依赖于外部世界中的事物,这可能会改变),那么IO
monad 就是你的朋友:
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 中计算”的公式。
查看您提供的链接,“计算”的常见用法似乎与单个单子值有关。摘录:
简单介绍——这里我们在 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
。似乎链接的页面在这个意义上使用计算- 计算作为单子值 - 至少在某些时候,在这种情况下,使用给定单子中的“计算”表达式是有意义的。
考虑IO
单子。类型值IO a
是对大量(通常是无限)行为的描述,其中行为是一系列 IO 事件(读取、写入等)。这样的值称为“计算”;在这种情况下,它是IO
monad 中的计算。