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
定义,前提是你有 return
和 fmap
)并将值放入 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 b
和 extract :: 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,我们可以正确实现 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
,但所有这些都不是全部,并且在计算时会引发错误。
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
定义,前提是你有 return
和 fmap
)并将值放入 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 b
和 extract :: 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,我们可以正确实现 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
,但所有这些都不是全部,并且在计算时会引发错误。