我来自python世界,但尽量使用函数式,并改变我的命令式思维。
现在我研究哈斯克尔,发现
list = [(x,y) | x<-[1,2,3], y<-[4,5,6]]
被翻译成
list' =
[1,2,3] >>= x ->
[4,5,6] >>= y ->
return (x,y)
我尝试逐步了解列表 monad 绑定链的分步处理:
绑定去查找为:
xs >>= f = concat (map f xs)
并且绑定是左关联
因此,正如我所理解的,在开始时首先执行绑定([1,2,3]>>= \x ->
[4,5,6]),结果为[4,5,6,4,5,6,4,5,6]然后下一个绑定 [4,5,6,4,5,6,4,5,6]>>= \y -> 返回 (x,y) 已执行
但是,如果它全部计算出来,它如何在 lambda 中看到 x? x 只是根本没有具体值的参数(lambda 可以随时用 varios 参数调用,我们如何在外面看到它并修复??如果它以某种方式可以看到它,它怎么知道 x 调用历史记录被 1,2,3 更改了?在我的理解中,一旦第一次绑定计算完成,只有结果 [4,5,6,4,5,6,4,5,6] 可用,它进入下一个绑定的第一个参数。
所以我不明白我如何阅读这个结构,以及它如何一步一步地逻辑产生正确的结果?
这是混淆的常见来源。lambda 的范围尽可能扩展,因此:
[1,2,3] >>= x ->
[4,5,6] >>= y ->
return (x,y)
相当于这个:
[1,2,3] >>= (x ->
[4,5,6] >>= (y ->
return (x,y)))
所以内 lambday -> …
在外 lambda 的范围内,x -> …
,这意味着x
和y
都在范围内。然后,您可以内联[]
monad 实例的>>=
和return
定义,并逐步完成评估:
concatMap (x ->
concatMap (y ->
[(x,y)]) [4,5,6]) [1,2,3]
concat
[ concatMap (y -> [(1,y)]) [4,5,6]
, concatMap (y -> [(2,y)]) [4,5,6]
, concatMap (y -> [(3,y)]) [4,5,6]
]
concat
[ concat [[(1,4)], [(1,5)], [(1,6)]]
, concat [[(2,4)], [(2,5)], [(2,6)]]
, concat [[(3,4)], [(3,5)], [(3,6)]]
]
concat
[ [(1,4), (1,5), (1,6)]
, [(2,4), (2,5), (2,6)]
, [(3,4), (3,5), (3,6)]
]
[ (1,4), (1,5), (1,6)
, (2,4), (2,5), (2,6)
, (3,4), (3,5), (3,6)
]
一种常见且更简洁的表达方式是使用Applicative
实例而不是Monad
实例,使用<$>
(fmap
) 和<*>
(ap
) 运算符或liftA2
:
(,) <$> [1,2,3] <*> [4,5,6]
liftA2 (,) [1,2,3] [4,5,6]