Kleisli 组合实际上是回答常见问题的最简单方法之一:单子有什么用?
我们可以用普通函数做的最有用的事情之一就是组合它们。给定f :: a -> b
and g :: b -> c
,我们可以先执行f
然后g
对结果执行,给我们g . f :: a -> c
。
只要我们只需要使用“普通”函数,那就太棒了。但是一旦我们开始在“现实世界”中编程,如果我们希望我们的语言保持纯粹和引用透明,我们很可能会遇到无法继续使用这些功能的情况。事实上,在这种情况下,其他没有 Haskell 原则性的语言放弃任何纯粹的伪装。考虑这些日常情况:
- 我们的函数
f
有时可能无法返回值。在许多其他语言中,这将通过返回来表示null
,但您不能将其输入g
. (您当然可以进行调整g
以应对null
输入,但这很快就会重复。)
在 Haskell 中,我们没有null
s,我们有Maybe
类型构造函数来显式地表示可能没有值。这意味着f
需要有 type a -> Maybe b
。出于同样的原因g
将有类型。b -> Maybe c
但是在这样做的过程中,我们失去了组合这两个函数的能力,因为我们不能直接将 type 的值提供Maybe b
给需要 type 输入的值b
。
- 的结果
f
可能取决于一些副作用(例如来自用户的输入,或数据库查询的结果)。这在不纯语言中没有问题,但在 Haskell 中,为了保持纯洁性,我们必须以 type 函数的形式实现它a -> IO b
。再一次,g
会以相同的形式结束,b -> IO c
我们已经失去了天真地组合这两个函数的能力。
我相信你可以看到这是怎么回事。在这两种情况下(并且可以很容易地提供更多,每个 monad 一个),我们不得不用 type 中的一个来替换一个简单的 type 函数,a -> b
以a -> m b
解决特定类型的“副作用” - 或者,如果您愿意,适用于函数结果的某种特定类型的“上下文”。这样一来,我们就失去了组合两个函数的能力,而这正是我们在“无副作用”世界中所拥有的。
monad 真正的目的是克服这一点,让我们为这种“不纯函数”恢复一种组合形式。当然,这正是 Kleisli 组合给我们的,一种形式的函数组合,a -> m b
它完全满足我们期望的函数组合的属性(即关联性,以及每种类型的“恒等函数”,这里是return :: a -> m a
)。
您对类型的“不完全组合”的建议(a -> m b) -> (b -> m c) -> (m a -> m c)
通常不会有用,因为结果函数需要一个单子值作为其输入,而在实践中单子值出现的主要方式是作为output s。但是,您仍然可以在需要时执行此操作,只需采用“正确的” Kleisli 组合,并通过>>=
.