Haskell 为什么kleisli合成需要纯值?

Haskell 为什么kleisli合成需要纯值?,haskell,monads,composition,kleisli,Haskell,Monads,Composition,Kleisli,这是kleisli组合的常见实现: kleisli :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c kleisli = \f g x -> f x >>= g 为什么它不期望一元上下文中的值呢?我相信有一个很好的理由。我只是没能看到而已 kleisli' :: Monad m => (a -> m b) -> (b -> m c) -> m a ->

这是kleisli组合的常见实现:

kleisli :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
kleisli = \f g x -> f x >>= g
为什么它不期望一元上下文中的值呢?我相信有一个很好的理由。我只是没能看到而已

kleisli' :: Monad m => (a -> m b) -> (b -> m c) -> m a -> m c
kleisli' = \f g x -> x >>= f >>= g

该类型似乎更易于组合,如果调用站点上只有纯值,则可以使用
return

a
b
的Kleisli箭头被定义为函数
a->mb
。让我们用符号表示
a~>b
(假定
m
)。组成这两个箭头意味着什么?它应具有以下类型:

(<=<) :: (b ~> c) -> (a ~> b) -> (a ~> c)

它附带了一个
Category
实例,该实例作为
()
操作符来实现此组合(但必须使用新类型包装)。

Kleisli组合实际上是回答常见问题的最简单的方法之一:monad有什么用

我们可以用普通函数做的最有用的事情之一就是组合它们。给定
f::a->b
g::b->c
,我们可以先对结果执行
f
,然后执行
g
,得到
g。f::a->c

只要我们只需要使用“普通”函数,那就太棒了。但是,一旦我们开始在“真实世界”中编程,如果我们希望我们的语言保持纯粹和引用透明,我们很可能会遇到无法继续使用这些函数的情况。事实上,在这种情况下,比哈斯凯尔更缺乏原则性的其他语言放弃了任何纯粹的伪装。考虑这些日常情况:

  • 函数
    f
    有时可能无法返回值。在许多其他语言中,这将通过返回
    null
    来表示,但您不能将其馈送到
    g
    。(当然,您可以调整
    g
    以处理
    null
    输入,但这将很快变得重复。)
在Haskell中,我们没有
null
s,我们有
Maybe
类型构造函数来显式表示可能没有值。这意味着
f
需要有类型
a->可能是b
g
将具有类型
b->可能是c
,原因相同。但是在这样做的过程中,我们已经失去了组合这两个函数的能力,因为我们不能直接将类型为
的值(可能是b
的值)输入到需要类型为
b
的值

  • f
    的结果可能取决于一些副作用(例如用户的输入或数据库查询的结果)。这在不纯净的语言中是没有问题的,但是在Haskell中,为了保持纯净,我们必须以类型为
    a->iob
    的函数的形式实现它。再一次,
    g
    将以相同的形式结束,
    b->ioc
    ,我们已经失去了天真地组合这两个函数的能力
我相信你能看到这一切。在这两种情况下(可以很容易地提供更多,每个monad一个),我们必须用
a->b
类型的函数替换
a->mb
类型的函数,以解释特定类型的“副作用”——或者,如果您愿意,应用于函数结果的特定类型的“上下文”。在这样做的过程中,我们失去了合成两种功能的能力,这是我们在“无副作用”的世界中所拥有的

单子的真正目的是克服这一点,让我们为这种“不纯函数”恢复一种构图形式。这当然正是Kleisli composition给我们的,一种形式为
a->mb
的函数组合,它完全满足我们期望的函数组合属性(即关联性,以及每种类型上的“标识函数”,这里是
return::a->ma


您建议的类型为
(a->mb)->(b->mc)->(ma->mc)
的“不完全合成”通常不会有用,因为结果函数需要一个一元值作为输入,而实际上一元值的主要产生方式是作为输出。但是,您仍然可以在需要时执行此操作,只需获取“适当的”Kleisli合成,并通过
>=
将一元值提供给它即可

你的定义是错误的,甚至没有进行类型检查。它应该是
\f g x->f x>>=g
简而言之,您的版本甚至不是“合成”。(实际)Kleisli合成与普通功能合成非常相似,它将其扩展到允许“具有副作用的功能”。(我可能稍后会将其扩展为一个答案,现在没有时间。)Kleisli态射是
a->MB
形式的函数,对于某些
a
b
。“合成”操作需要获取两个态射并返回一个态射。因此,每个函数必须具有相同的“形状”。我们想要这个,这样我们就可以谈论克莱斯利类别。您可以定义另一个操作,但这不是通常意义上的“组合”(从技术上讲,是范畴理论给出的组合)。@bob,
Control.Monad.
现在的问题是,为什么Kleisli箭头有用?它们是,因为它们是单子的精髓。如果我们只有一元值,应用函子就足够了。Monad还具有从以前的CD生成的纯值创建新的计算描述(“配方”、“说明”)的附加功能,并将它们组合成一个组合CD(通过
>=
)。单子的结合律意味着
(f>=>g)=>h==f>=>(g>=>h)
,其中
=>
由等式定义
(m>=f)>=g==m>=(f>=>g)
。(另见:“单子公理:克莱斯利构成一个范畴”。)我被我那类型的所谓对称性
(MA->MC)
误导了。然而,真正的对称性是
(a->mc)
,因为kleisli构图都是关于kleisli箭头的。我想这正是你在报告中所说的
(<=<) :: (b -> m c) -> (a -> m b) -> (a -> m c)
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }