data M a = M a deriving (Show)
unitM a = M a
bindM (M a) f = f a
joinM :: M (M a) -> M a
joinM m = m `bindM` id
joinM' :: M a -> a
joinM' m = m `bindM` id
请注意,joinM (M 0)
无法输入检查,而joinM' (M 0)
则可以。
我的问题:为什么joinM
定义为M (M a) -> M a
但不定义为M a -> a
?
根据我的理解,
unitM
将值a
放入monad M a
中
joinM
从monad a
M a
所以joinM
应该适用于任何monad,即不一定是M (M a)
等嵌套的,对吗?
答案 0 :(得分:8)
monad的观点是无法从中获取值。如果join
类型为m a -> a
,则IO
monad将完全无用,因为您可以自由地提取值。 monad的观点是你可以将计算链接在一起(>>=
可以用join
定义,只要你有return
和fmap
)并将值放入monadic中上下文,但你不能(一般来说)把它们拿出来。
在您的特定情况下,您已经定义了基本上是身份monad的内容。在这种情况下,很容易提取值;你只需去除M
层,继续你的生活。但是对于一般的monad来说不是这样,所以我们限制join
的类型,以便更多东西可以是monad。
顺便提一下,bindM
的类型不正确。 >>=
的一般类型是
(>>=) :: Monad m => m a -> (a -> m b) -> m b
您的功能有类型
bindM :: M a -> (a -> b) -> b
请注意,您的类型更为通用。因此,再次,在特定的情况下,您可以放松对joinM
的要求更宽松,而特定的monad不能。尝试为bindM
提供M a -> (a -> M b) -> M b
的显式类型签名,然后查看两个连接函数是否仍然是类型检查。
答案 1 :(得分:4)
给定类型构造函数M :: * -> *
和类型a
,请考虑以下类型序列
a, M a, M (M a), M (M (M a)), ...
如果我们有多态函数return :: b -> M b
和extract :: M b -> b
(您的替代join
),我们可以将上面任何类型的值转换为上面的任何其他类型。实际上,我们可以使用这两个函数添加和删除M
,并适当选择类型b
。换句话说,我们可以将这两种方式移动到右和左。
在monad中,我们可以无限制地移动到 right (使用return
)。我们也可以在几乎所有地方移动到 left :重要的例外是我们无法从M a
移动到a
。这是通过join :: M (M c) -> M c
实现的,其extract :: M b -> b
类型仅限于案例b = M c
。基本上,我们可以移动 left (与extract
一样),但仅当我们最终处于至少有一个M
的类型时 - 因此,没有进一步左边比M a
。
正如卡尔在评论中提到的那样,这种限制使得有更多的单子成为可能。例如,如果M = []
是列表monad,我们可以正确实施return
和join
,但不能extract
。
return :: a -> [a]
return x = [x]
join :: [[a]] -> [a]
join xss = concat xss
而extract :: [a] -> a
不能是一个完整的函数,因为extract [] :: a
的类型很好,但却试图从空列表中提取a
类型的值。众所周知的理论结果是没有总表达式可以具有多态类型... :: a
。我们可以undefined :: a
,fromJust Nothing :: a
或head [] :: a
,但所有这些都不是全部,并且在评估时会引发错误。