haskell - 连接函数的类型

haskell - the type of join function

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 M a

获取值 a

所以 joinM 应该真正适用于任何 monad,即不一定是嵌套的,例如 M (M a),对吗?

monad 的要点是您不能从它们中获取值。如果 join 的类型是 m a -> a 那么 IO monad 就完全没用了,因为你可以自由地提取值。 monad 的要点是你可以将计算链接在一起(>>= 可以根据 join 定义,前提是你有 returnfmap)并将值放入 monadic上下文,但你不能(通常)把它们弄出来。

在您的特定情况下,您已经定义了本质上是 identity monad 的内容。在那种情况下,很容易提取价值;您只需剥离 M 层并继续您的生活。但是对于一般的monad就不是这样了,所以我们限制了join的类型,让更多的东西可以是monad。

顺便说一下,您的 bindM 类型不正确。 >>=的一般类型是

(>>=) :: Monad m => m a -> (a -> m b) -> m b

你的函数有类型

bindM :: M a -> (a -> b) -> b

请注意,您的类型比较笼统。因此,同样,在您的 specific 情况下,您可以放宽对 joinM 的要求,而特定的 monad 则不能。尝试给 bindM 一个 M a -> (a -> M b) -> M b 的显式类型签名,然后查看您的两个连接函数是否仍然进行类型检查。

给定类型构造函数 M :: * -> * 和类型 a,考虑以下类型序列

a, M a, M (M a), M (M (M a)), ...

如果我们有多态函数 return :: b -> M bextract :: M b -> b(您的选择 join),我们可以将上述任何类型的值转换为上述任何其他类型。实际上,我们可以使用这两个函数根据需要添加和删除 M,并适当地选择类型 b。更随意的话,我们可以在这样的类型序列中向移动。

相反,在 monad 中,我们可以无限制地向右侧移动(使用return)。我们也可以几乎在任何地方移动到左边:重要的例外是我们不能从M a移动到a。这是通过 join :: M (M c) -> M c 实现的,extract :: M b -> b 的类型仅限于 b = M c 的情况。所以本质上,我们可以移动 left(与 extract 一样),但前提是我们最终得到的类型至少有一个 M——因此,向左不超过 M a.

正如 Carl 在上面的评论中提到的那样,此限制使得拥有更多 monad 成为可能。例如,如果 M = [] 是列表 monad,我们可以正确实现 returnjoin 但不能实现 extract.

return :: a -> [a]
return x = [x]

join :: [[a]] -> [a]
join xss = concat xss

相反,extract :: [a] -> a 不能是总函数,因为 extract [] :: a 的类型很好,但会尝试从空列表中提取类型为 a 的值。没有总表达式可以具有多态类型... :: a,这是一个众所周知的理论结果。我们可以有 undefined :: afromJust Nothing :: ahead [] :: a,但所有这些都不是全部,并且在计算时会引发错误。