关于fp-course中Haskell join函数实现的技术疑问
解析fp-course中Monad的join函数实现
先看题目给出的join实现及相关代码:
-- | Flattens a combined structure to a single structure. -- -- >>> join ((1 :. 2 :. 3 :. Nil) :. (1 :. 2 :. Nil) :. Nil) -- [1,2,3,1,2] -- -- >>> join (Full Empty) -- Empty -- -- >>> join (Full (Full 7)) -- Full 7 -- -- >>> join (+) 7 -- 14 join :: Monad k => k (k a) -> k a join x = id =<< x
已知相关类型定义:
id :: a -> a(=<<) :: (a -> k b) -> k a -> k b
为什么id可以作为(=<<)的第一个参数?
从类型匹配的角度拆解:
join的参数x类型是k (k a),也就是嵌套的Monad结构。- 把
(=<<)类型里的泛型a替换成k a,此时(=<<)的类型会变成:(k a -> k b) -> k (k a) -> k b。 id的类型是a -> a,我们可以将其中的a实例化为k a,此时id的类型就变成k a -> k a,完全匹配(=<<)第一个参数要求的k a -> k b(这里b等同于a)。
所以从类型规则上看,id完全符合(=<<)第一个参数的要求。
为什么join x = id =<< x是正确的实现?
首先明确:(=<<)是>>=的翻转写法,f =<< x等价于x >>= f,所以id =<< x其实就是x >>= id。
而join的核心功能是把嵌套的Monad结构k (k a)平铺成单层的k a,这正好可以通过x >>= id实现:
>>=的作用是取出Monad内部的值,传给指定函数,再把结果重新包装成Monad。- 当传入
id作为函数参数时,id会直接返回内层的k a结构,>>=则会自动把这个内层结构“展开”到外层的Monad上下文中,最终得到单层的k a,完全契合join的设计目标。
结合题目示例验证:
- List类型:
join [[1,2,3],[1,2]]等价于[[1,2,3],[1,2]] >>= id,>>=会把每个子列表的元素取出合并,得到[1,2,3,1,2]。 - Maybe类型:
join (Full (Full 7))等价于Full (Full 7) >>= id,>>=取出内层的Full 7并返回,结果为Full 7;join (Full Empty)等价于Full Empty >>= id,取出Empty返回,结果就是Empty。 - 函数类型:
join (+)等价于(+) >>= id,函数Monad的>>=会把输入值同时传给(+)和id,最终得到\x -> x + x,所以join (+) 7的结果是14。
内容的提问来源于stack exchange,提问作者zichao liu
相关产品推荐
相关产品推荐

