现在我要求相反的情况。我对范畴论有着扎实的掌握,并不害怕追逐图表、Yoneda引理或导出函子(事实上也知道单子和范畴意义下的伴随)。
有人能给我提供一个在函数式编程中单子的清晰简明的定义吗?例子越少越好:有时一个清晰的概念比一百个腼腆的例子更有意义。Haskell可以作为演示语言,但我不挑剔。
这个问题有一些很好的答案:单子作为伴随
更重要的是,Derek Elkins的"TMR#13中用范畴论计算单子"文章应该有您寻找的构造:http://www.haskell.org/wikiupload/8/85/TMR-Issue13.pdf
最后,也许这真的是您要寻找的最接近的内容,您可以直接查看Moggi在1988-91年对该主题的开创性论文:http://www.disi.unige.it/person/MoggiE/publications.html
特别是请参见“计算和单子的概念”。
我的自己的简短而不够精确的观点:
从一个类别Hask
开始,其对象是Haskell类型,其态射是函数。函数也是Hask
中的对象,乘积也是如此。因此,Hask
是笛卡尔闭合的。现在引入一个箭头,将Hask
中的每个对象映射到MHask
中,后者是Hask
对象的子集。单位!接下来,引入一个箭头,将Hask
上的每个箭头映射到MHask
上的箭头。这给了我们映射,并使MHask
成为协变的自函子。现在引入一个箭头,将从MHask
中生成的每个MHask
对象(通过单位)映射到生成它的MHask
对象。连接!从那里开始,MHask
就是一个单子(更精确地说是一个单调的自函子)。
我相信上面的内容存在不足之处,这也是为什么如果你在寻求形式主义方面的知识,我真的建议你特别参考Moggi论文。
Hask
的对象是 Haskell 中的类型,而不是值。箭头表示 Haskell 函数的定义域和值域分别为相应的类型。 - Lambdageekclass Monad m where
join :: m (m a) -> m a
return :: a -> m a
fmap :: (a -> b) -> m a -> m b
>>=
)可以被定义为x >>= f = join (fmap f x)
>>=
而有用,但是 (fmap
, join
, return
) 这三个操作更能阐明单子(imo)的本质。当我最终理解了"一个单子只不过是范畴内自函子构成的幺半群"实际上意味着什么时,我受到了启发。 - dave4420好的,使用 Haskell 的术语和示例...
在函数式编程中,单子是一种用于具有类型 *-> *
的数据类型的组合模式。
class Monad (m :: * -> *) where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(在Haskell中,类别还有更多内容,但那些是重要的部分。)
如果一个数据类型能够实现monad接口并满足三个实现条件,那么它就是一个monad。这些条件被称为“monad定律”,具体解释可以参考较长的说明。我将这些法则总结为"(>>= return)
是一个恒等函数,(>>=)
是可结合的"。即使可以更加精确地表达,但其实不会比这更复杂了。
而这就是一个monad的全部内容。只要你能够保持这些行为属性,同时实现该接口,那么你就拥有了一个monad。
这个解释可能比你预期的要简短。因为monad接口的抽象程度非常高。抽象的程度是为什么如此多的不同事物都可以被建模为monad的一部分原因。
更不容易理解的是,尽管接口如此抽象,但它允许通用建模任何控制流模式,无论实际的monad实现如何。这就是为什么GHC的base
库中的Control.Monad
包具有像when
、forever
等组合子的原因。这也是为什么能够明确地对任何monad实现进行抽象是强大的,特别是在类型系统的支持下。
fmap
!你需要fmap
! - sclv fmap
也可以用>>=
和return
来定义:fmap f m = m >>= return . f
。 - hammar一个单子(Monad)是在自函子范畴中的幺半群,这有什么问题呢?.
开玩笑的,就我个人而言,我认为单子在Haskell和函数编程中的使用更好地从“单子作为接口”(如Carl和Dan的回答所示)而非“单子作为范畴论术语”的角度解释。不得不承认,当我在一个真实项目中使用另一种语言的monadic库时,我才真正理解了整个单子概念。
你提到你不喜欢所有“大量示例”的教程。是否有人向你介绍过Akward squad 论文?它主要关注IO monad,但其介绍给我们一个好的技术和历史背景,说明Haskell最初为什么拥抱单子概念。
()
);单子只是决定值如何通过一系列计算进行操作的。State
不影响值,但更新一个第二值,该值在后台从计算到计算传递;如果Maybe
遇到 Nothing
,则短路计算;List
允许你通过传递变量数量的值;IO
让你以安全的方式传递不纯的值。我使用过的更专业化的单子,如 Gen
和 Parsec 解析器也是类似的。由于您理解范畴论意义下的单子,我将解释您的问题与函数式编程中单子的表达方式有关。 因此,我的答案避免了任何有关单子是什么或其含义或用途的解释。
答案:在Haskell中,单子以某个范畴的内部语言的Kleisli三元组的(内部化)映射的形式呈现。
解释:很难精确描述“Hask”类别的属性,并且这些属性对于理解Haskell的单子表示方法基本上没有影响。 因此,对于这个讨论,更有用的是将Haskell视为某个类别C的内部语言。 Haskell函数定义了C中的态射,而Haskell类型是C中的对象,但是进行这些定义的特定类别并不重要。
参数化数据类型,例如data F a = ...
,是对象映射,例如F:|
C| -> |
C|
。
在Haskell中,单子的通常描述是以Kleisli triple(或Kleisli extension)形式呈现:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
其中:
m
是对象映射m :|
C| -> |
C|
return
是对象上的单元操作>>=
(Haskeller称之为"bind")是态射上的扩展操作,但其前两个参数已经交换了位置(与扩展的通常签名(-)* : (a -> m b) -> m a -> m b
相比)(这些映射本身作为C中的态射家族进行内部化,这是可能的,因为m :|
C| -> |
C|
)。
因此,如果你已经接触过Haskell的do记法,它就是Kleisli范畴的内部语言。