我看到下面的例子来自Writer一节。
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = Writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b)
结果是:
ghci> runWriter multWithLog
(15,["Got number: 3","Got number: 5"])
我知道在do表示法中<->将从上下文中提取值。因此,根据Writer的声明,a和b应该是格式为(Int, [String])的元组。我觉得a和b在return (a*b)中应该是两个整数,否则我们不能做乘法。
我的误解是什么?有人能帮忙吗?多谢。
单子中的主要"工作"是在>>= (bind)函数中产生的。单子是某种计算生成器。所以如果你想知道在具体的Monad中如何进行计算,你需要打开它的>>=和返回函数的实现。
。Writer的单子实现:
instance (Monoid w) => Monad (Writer w) where
return a = Writer (a, mempty)
m >>= k = Writer $ let
(a, w) = runWriter m
(b, w') = runWriter (k a)
in (b, w `mappend` w')
所以它需要另一个作家m,得到它的计算结果a(在你的情况下是Int)和额外的上下文w ([String])通过在它上执行runWriter。然后将结果提供给函数k,该函数返回另一个Writer。在得到结果后,通过应用 mapappend (在list为++的情况下)将上下文组合为。
所以字符串的组合发生在w mappend
w'。这一切都发生在幕后。
可以看到,>>=只向函数k提供结果a (Int),没有任何上下文([String])。这就是为什么你的代码中没有任何对。
也许如果你重写你的代码没有"do"语法糖,它会更清楚:
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = Writer (x, ["Got number: " ++ show x])
multWithLog =
logNumber 3 >>=
a -> logNumber 5 >>=
b -> return (a*b)
暂时忽略logNumber
的定义,只关心它的签名:
logNumber :: Int -> Writer [String] Int
我们知道Writer s
是一个单子(给定Monoid s
)。让我们暂时使用一个类型同义词StringWriter
来使事情更清楚:
type StringWriter = Writer [String]
logNumber :: Int -> StringWriter Int
请记住,Writer s
是一个单子,而StringWriter
只是该单子的类型同义词。我们还改变了multWithLog
:
multWithLog :: StringWriter Int
现在应该清楚了,在StringWriter
上下文中包装的值是一个Int
,而不是一对。现在,回到你的语句:
我知道在do表示法中
<-
将从上下文中提取值。
此时,被提取的值的类型应该是Int
。
你的第一个陈述,a
和b
是元组,是不完全正确的。
如果我们有像
foo = do
...
baz <- bar
quux
如果bar :: m a
是,那么我们可以将baz
视为quux
中的a
类型的值,因为<-
(大致)是
foo = do
...
bar >>= baz -> quux
Zeta很好地解释了为什么a
和b
是Int
而不是成对的。
我认为混淆的主要原因是最后一行
return (a*b)
如果a = 3
和b = 5
,这不应该导致Writer (15, [])
吗?是的,但是["Got number: 3","Got number: 5"]
从何而来呢?
答案在于>>=
的实现。让我们去除do
符号的语法糖:
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b)
等价于
logNumber 3 >>= (a ->
logNumber 5 >>= (b ->
return (a*b)))
等价于
Writer (3, ["Got number: 3"]) >>= (a ->
Writer (5, ["Got number: 5"]) >>= (b ->
Writer (a*b, [])))
等价于
Writer (3, ["Got number: 3"]) >>= (a ->
Writer (5, ["Got number: 5"]) >>= (b ->
Writer (3*5, [])))
记住,>>=
是一个二进制函数(接受两个参数的函数),返回一个Writer (Int, [String])
,即一个Writer
,一个Int
和一个日志。从函数>>=
返回的日志是第一个参数的日志["Got number 3", "Got number 5"]
和第二个参数的日志[]
的组合。
旁注:如果您查看括号,日志附加的实际顺序将是
["Got number: 3"] ++ (["Got number: 5"] ++ [])