我一直在阅读类别理论中的单子。对monad的一个定义是使用一对伴随函子。一个单子通过使用这些函子的往返来定义。显然,附加语在范畴论中非常重要,但是我还没有看到关于伴随函子对Haskell monads的任何解释。有没有人想过?
我一直在阅读类别理论中的单子。对monad的一个定义是使用一对伴随函子。一个单子通过使用这些函子的往返来定义。显然,附加语在范畴论中非常重要,但是我还没有看到关于伴随函子对Haskell monads的任何解释。有没有人想过?
Answers:
编辑:只是为了好玩,我会做正确的。原始答案保留在下面
现在,类别扩展的当前附加代码位于附加程序包中:http : //hackage.haskell.org/package/adjunctions
我将简单明了地通过状态monad进行工作。这段代码Data.Functor.Compose
是在Transformers软件包中使用的,但是它是自包含的。
f(D-> C)和g(C-> D)之间的附加词,写为f-| g,可以通过多种方式表征。我们将使用counit / unit(epsilon / eta)描述,该描述给出了两个自然变换(函子之间的同构)。
class (Functor f, Functor g) => Adjoint f g where
counit :: f (g a) -> a
unit :: a -> g (f a)
请注意,单位中的“ a”实际上是C中的身份函子,单位中的“ a”实际上是D中的身份函子。
我们还可以从单位/单位定义中恢复hom-set附加定义。
phiLeft :: Adjoint f g => (f a -> b) -> (a -> g b)
phiLeft f = fmap f . unit
phiRight :: Adjoint f g => (a -> g b) -> (f a -> b)
phiRight f = counit . fmap f
无论如何,我们现在都可以像这样从我们的单位/联合附属中定义一个Monad:
instance Adjoint f g => Monad (Compose g f) where
return x = Compose $ unit x
x >>= f = Compose . fmap counit . getCompose $ fmap (getCompose . f) x
现在我们可以实现(a,)和(a->)之间的经典附加词:
instance Adjoint ((,) a) ((->) a) where
-- counit :: (a,a -> b) -> b
counit (x, f) = f x
-- unit :: b -> (a -> (a,b))
unit x = \y -> (y, x)
现在是类型的同义词
type State s = Compose ((->) s) ((,) s)
如果将其加载到ghci中,我们可以确认State正是我们的经典state monad。请注意,我们可以采用相反的组成并获得Costate Comonad(又名comonad商店)。
我们还可以通过这种方式将其他附加词做成单子(例如(Bool,)Pair),但它们却是一种奇怪的单子。不幸的是,我们无法以令人愉快的方式直接在Haskell中进行诱导Reader和Writer的附加操作。我们可以做Cont,但是正如copumpkin所描述的,这需要来自相反类别的附加语,因此它实际上使用了“ Adjoint”类型类的另一个“形式”,它使一些箭头反向。该形式还可以在附加程序包的其他模块中实现。
德里克·埃尔金斯(Derek Elkins)在《 Monad Reader 13-用范畴论计算单子》中的文章以不同的方式介绍了该材料:http : //www.haskell.org/wikiupload/8/85/TMR-Issue13.pdf
另外,Hinze最近在“ Kan Extensions for Program Optimization”一文中还通过Mon
和之间的附加内容逐步构建了列表monad Set
:http : //www.cs.ox.ac.uk/ralf.hinze/Kan.pdf
旧答案:
两个参考。
1)范畴扩展一如既往地提供了附加词及其从中产生的单子的表示。和往常一样,最好考虑一下,但对文档的要求很轻:http : //hackage.haskell.org/packages/archive/category-extras/0.53.5/doc/html/Control-Functor-Adjunction.html
2)-咖啡馆还对附属的作用进行了有希望的但简短的讨论。其中一些可能有助于解释类别引伸:http : //www.haskell.org/pipermail/haskell-cafe/2007-December/036328.html
unit :: b -> (a -> (a,b))
。
德里克·埃尔金斯(Derek Elkins)最近在晚餐时向我展示了Cont Monad是如何通过将(_ -> k)
自反函子与自身组合而产生的,因为它恰好是自伴的。这就是您摆脱(a -> k) -> k
困境的方式。但是,它的协同作用会导致双重否定消除,这无法用Haskell编写。
有关说明和证明这一点的某些Agda代码,请参见http://hpaste.org/68257。
这是一个旧线程,但是我发现这个问题很有趣,所以我自己做了一些计算。希望Bartosz还在,并且可能会读到此。
实际上,在这种情况下,艾伦伯格-摩尔(Eilenberg-Moore)结构的确提供了非常清晰的画面。(我将在类似Haskell的语法中使用CWM表示法)
设T
monad < T,eta,mu >
(eta = return
和mu = concat
)为列表,并考虑T代数h:T a -> a
。
(请注意,这T a = [a]
是一个免费的monoid <[a],[],(++)>
,即身份[]
和乘法(++)
。)
根据定义,h
必须满足h.T h == h.mu a
和h.eta a== id
。
现在,一些简单的图追踪证明h
实际上在(定义x*y = h[x,y]
)上诱导了一个单面体结构,并且h
对该结构变成了一个单面体同构。
相反,< a,a0,* >
在Haskell中定义的任何类单元结构自然定义为T代数。
通过这种方式(h = foldr ( * ) a0
,一个功能“替代”(:)
用 (*)
,并映射[]
到a0
,身份)。
因此,在这种情况下,T代数的类别只是可在HaskMon,Haskell中定义的类半圆结构的类别。
(请检查T代数中的态射实际上是类单态同态。)
它还将列表表征为HaskMon中的通用对象,就像Grp中的免费乘积,CRng中的多项式环等。
与上述结构相对应的裁定为 < F,G,eta,epsilon >
哪里
F:Hask -> HaskMon
,其类型为“生成的自由monoid a
”,即[a]
,G:HaskMon -> Hask
,健忘的函子(忘记乘法),eta:1 -> GF
,天然转化定义通过\x::a -> [x]
,epsilon: FG -> 1
,由上面的折叠函数定义的自然变换(从自由单半体到其商单半体的“规范超越”)接下来,还有另一个“克莱斯里类别”和相应的附加语。您可以检查它是否只是具有态射的Haskell类型的类别a -> T b
,其组成由所谓的“ Kleisli组成”给出(>=>)
。典型的Haskell程序员会发现此类别更加熟悉。
最终,如CWM中所示,T代数的类别(分别为Kleisli类别)成为在适当意义上定义列表单子T的裁量类别中的最终对象(分别为初始)。
我建议对二叉树函子T a = L a | B (T a) (T a)
进行类似的计算,以检查您的理解。
我已经为Eilenberg-Moore的任何monad找到了辅助函子的标准构造,但是我不确定它是否对问题有任何启示。构造中的第二类是T代数。AT代数将“产品”添加到初始类别。
那么,对于列表monad它将如何工作?list monad中的函子由类型构造Int->[Int]
函数(例如)和功能映射(例如map到list的标准应用程序)组成。代数添加了从列表到元素的映射。一个示例是将一个整数列表中的所有元素相加(或相乘)。函子F
采用任何类型,例如Int,并将其映射到Int列表中定义的代数,其中乘积由一元连接定义(反之亦然,连接定义为乘积)。健忘的函子G
取一个代数就忘记了乘积。一对F
,G
,伴随函子的随后用于构建单子以通常的方式。
我必须说我没有一个明智的选择。
如果您有兴趣,可以考虑以下非专家对编程语言中monad和附加词的作用的看法:
首先,对于给定的monad,存在T
Kleisli类别的唯一附加语T
。在Haskell中,monad的使用主要限于此类别中的运算(该类别实质上是自由代数,无商数的类别)。实际上,使用Haskell Monad所能做的就是a->T b
通过使用do表达式(>>=)
等来组合一些类型的Kleisli态射像,以创建新的态射像。在这种情况下,单子的作用仅限于符号的经济性。一个人利用态素的关联性来写(比如说)[0,1,2]
而不是(Cons 0 (Cons 1 (Cons 2 Nil)))
,即可以将序列写为序列而不是树。
甚至不需要使用IO monad,因为当前的Haskell类型系统功能强大,足以实现数据封装(现有类型)。
这是我对您最初的问题的回答,但我很好奇Haskell专家对此有何评论。
另一方面,正如我们已经指出的,单子与(T-)代数的附加词之间也存在1-1对应关系。用MacLane的术语来说,伴随是“表达类别对等的一种方式”。在典型的附加条件中<F,G>:X->A
,F
某种形式的“自由代数生成器”和G是“健忘函子”,相应的单子将(通过使用T代数)描述如何(以及何时)构造的代数结构A
。的对象X
。
在Hask和list monad T的情况下,T
引入的结构是类半体的结构,这可以帮助我们通过类半体理论提供的代数方法来建立代码的属性(包括正确性)。例如,该函数foldr (*) e::[a]->a
可以很容易地被视为关联操作,只要它<a,(*),e>
是一个monoid,便可以被编译器用来优化计算(例如,通过并行性)的事实。另一个应用程序是使用分类方法对函数式编程中的“递归模式”进行识别和分类,以期(部分)处置“函数式编程的起点” Y(任意递归组合器)。
显然,这种应用是类别理论的创建者(MacLane,Eilenberg等)的主要动机之一,即建立类别的自然对等关系,并将一种类别中的知名方法转移到另一类别(例如,拓扑空间的同构方法,编程的代数方法等)。在这里,伴随和单子是利用类别的这种联系的必不可少的工具。(顺便提一下,单子(及其双偶,共母)的概念是如此笼统,以至于甚至可以定义Haskell类型的“同调”,但我还没有想到。)
至于您提到的非确定性函数,我要说的要少得多。如果<F,G>:Hask->A
某个类别A
的附加语定义了列表monad T
,则必须有一个唯一的“比较函子” K:A->MonHask
(在Haskell中可定义的类单元体的类别),请参阅CWM。实际上,这意味着您要关注的类别必须是某种受限形式的Monoid类别(例如,它可能缺少一些商但不是自由代数)才能定义列表单子。
最后,一些评论:
我在上一篇文章中提到的二叉树函子很容易推广到任意数据类型
T a1 .. an = T1 T11 .. T1m | ...
。也就是说,Haskell中的任何数据类型自然会定义一个monad(以及相应的代数类别和Kleisli类别),这仅仅是Haskell中任何数据构造函数的总和。这就是为什么我认为Haskell的Monad类不只是语法糖(当然,这在实践中非常重要)的另一个原因。