haskell - 连接函数的类型

时间:2018-01-17 19:45:38

标签: haskell monads

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 ajoinM从monad a

获取值M a

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

2 个答案:

答案 0 :(得分:8)

monad的观点是无法从中获取值。如果join类型为m a -> a,则IO monad将完全无用,因为您可以自由地提取值。 monad的观点是你可以将计算链接在一起(>>=可以用join定义,只要你有returnfmap)并将值放入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 bextract :: 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,我们可以正确实施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,但所有这些都不是全部,并且在评估时会引发错误。