[英]Initial algebra for rose trees
As far as I understand, recursive data types from Haskell correspond to initial algebras of endofunctors from the Hask
category [ 1 , 2 ]. 据我明白,从Haskell的递归数据类型对应于endofunctors的初始代数从
Hask
类别[ 1 , 2 ]。 For example: 例如:
data Nat = Zero | Succ Nat
data Nat = Zero | Succ Nat
data Nat = Zero | Succ Nat
, correspond to the initial algebra of the endofunctor F(-) = 1 + (-)
. data Nat = Zero | Succ Nat
,对应于endofunctor F(-) = 1 + (-)
的初始代数。 data List a = Nil | Cons a (List a)
data List a = Nil | Cons a (List a)
data List a = Nil | Cons a (List a)
, correspond to the initial algebra of the endofunctor F(A, -) = 1 + A × (-)
. data List a = Nil | Cons a (List a)
,对应于endofunctor F(A, -) = 1 + A × (-)
的初始代数。 However, it's not clear to me what the endofunctor corresponding to the rose trees should be: 但是,我不清楚对应玫瑰树的endofunctor应该是什么:
data Rose a = Node a (List (Rose a))
What confuses me is that there are two recursions: one for the rose tree and the other for the list. 令我困惑的是,有两个递归:一个用于玫瑰树,另一个用于列表。 According to my calculations, I would get the following functor, but it doesn't seem right:
根据我的计算,我会得到以下仿函数,但它似乎不正确:
F(A, •, -) = A × (1 + (-) × (•))
Alternatively, rose trees can be defined as mutually recursive data types: 或者,可以将玫瑰树定义为相互递归的数据类型:
data Rose a = Node a (Forest a)
type Forest a = List (Rose a)
Do mutually recursive data types have an interpretation in category theory? 相互递归数据类型在类别理论中有解释吗?
I would discourage talk of "the Hask Category" because it subconsciously conditions you against looking for other categorical structure in Haskell programming. 我会劝阻谈论“Hask类别”,因为它潜意识地阻止你在Haskell编程中寻找其他分类结构。
Indeed, rose trees can be seen as the fixpoint of an endofunctor on types-and-functions, a category which we might be better to call Type
, now that Type
is the type of types. 实际上,玫瑰树可以看作是类型和函数的endofunctor的固定点,我们可能更好地调用
Type
,现在Type
是类型的类型。 If we give ourselves some of the usual functor kit... 如果我们给自己一些通常的仿函数套件......
newtype K a x = K a deriving Functor -- constant functor
newtype P f g x = P (f x, g x) deriving Functor -- products
...and fixpoints... ......和固定点......
newtype FixF f = InF (f (FixF f))
...then we may take ......那我们可以接受
type Rose a = FixF (P (K a) [])
pattern Node :: a -> [Rose a] -> Rose a
pattern Node a ars = InF (P (K a, ars))
The fact that []
is itself recursive does not prevent its use in the formation of recursive datatypes via Fix
. []
本身是递归的这一事实并不妨碍它通过Fix
在形成递归数据类型时使用。 To spell out the recursion explicitly, we have nested fixpoints, here with bound variable names chosen suggestively: 为了明确地拼出递归,我们有嵌套的固定点,这里有一些暗示选择的绑定变量名:
Rose a = μrose. a * (μlist. 1 + (rose * list))
Now, by the time we've arrived in the second fixpoint, we have a type formula 现在,当我们到达第二个固定点时,我们有一个类型公式
1 + (rose * list)
which is functorial (indeed, strictly positive) in both rose
and list
. 在
rose
和list
中都是functorial(确实是严格正面的)。 One might say it is a Bifunctor
, but that's unnecessary terminology: it's a functor from (Type, Type)
to Type
. 有人可能会说它是
Bifunctor
,但那是不必要的术语:它是从(Type, Type)
到Type
的Bifunctor
函数。 You can make a Type -> Type
functor by taking a fixpoint in the second component of the pair, and that's just what happened above. 您可以通过在对的第二个组件中使用一个固定点来创建一个
Type -> Type
仿函数,这就是上面发生的事情。
The above definition of Rose
loses an important property. Rose
的上述定义失去了重要的属性。 It is not true that 这不是真的
Rose :: Type -> Type -- GHC might say this, but it's lying
merely that Rose x :: Type
if x :: Type
. 仅仅是
Rose x :: Type
if x :: Type
。 In particular, 特别是,
Functor Rose
is not a well typed constraint, which is a pity, as intuitively, rose trees ought to be functorial in the elements they store. 不是一个很好的类型约束,这是一个遗憾,直观地说,玫瑰树应该是他们存储的元素的花边。
You can fix this by building Rose
as itself being the fixpoint of a Bifunctor
. 你可以通过构建
Rose
作为Bifunctor
的固定点来解决这个Bifunctor
。 So, in effect, by the time we get to lists, we have three type variables in scope, a
, rose
and list
, and we have functoriality in all of them. 所以,实际上,当我们到达列表时,我们在范围中有三个类型变量,
a
, rose
和list
,并且我们在所有这些变量中都有functoriality。 You need a different fixpoint type constructor, and a different kit for building Bifunctor
instances: for Rose
, life gets easier because the a
parameter is not used in the inner fixpoint, but in general, to define bifunctors as fixpoints requires trifunctors, and off we go! 你需要一个不同的 fixpoint类型构造函数,以及一个用于构建
Bifunctor
实例的不同工具包:对于Rose
,生命变得更容易,因为a
参数没有在内部修复点中使用,但一般来说,将bifunctors定义为fixpoints需要trifunctors,然后我们关闭走!
This answer of mine shows how to fight the proliferation by showing how indexed types are closed under a fixpoint-of-functor construction. 我的这个答案显示了如何通过展示索引类型在仿函数修复点构造下的关闭来对抗扩散。 That's to say, work not in
Type
but in i -> Type
(for the full variety of index types i
) and you're ready for mutual recursion, GADTs, and so on. 也就是说,不是在
Type
工作,而是在i -> Type
(对于各种索引类型i
),你已准备好进行相互递归,GADT等等。
So, zooming out, rose trees are given by mutual fixpoints, which have a perfectly sensible categorical account, provided you see which categories are actually at work. 因此,放大,玫瑰树是由相互固定点给出的,它们具有完全合理的分类帐户,只要您看到哪些类别实际上在起作用。
This is not really an answer to the question you're asking, but perhaps interesting anyway. 这不是你问的问题的真正答案,但无论如何也许有趣。 Note that with
请注意
Rose a = a * List (Rose a)
List a = 1 + a * List a
and the fact that *
distributes over +
, you have 以及
*
分配超过+
的事实,你有
Rose a
= {- definition of `Rose` -}
a * List (Rose a)
= {- definition of `List` -}
a * (1 + Rose a * List (Rose a))
= {- `*` distributes over `+` -}
a + a * Rose a * List (Rose a)
= {- `*` is commutative -}
a + Rose a * a * List (Rose a)
= {- definition of `Rose` -}
a + Rose a * Rose a
(the equality really denotes isomorphism). (等式确实表示同构)。 So you might as well have defined
所以你可能已经定义了
Rose a = a + Rose a * Rose a
or in Haskell, 或者在Haskell,
data Rose a = Leaf a | Bin (Rose a) (Rose a)
Which is to say, rose trees are isomorphic to ordinary (leaf-labelled) binary trees, and which clearly form a normal initial algebra. 也就是说,玫瑰树与普通(叶标记)二叉树同构,并且明显形成正常的初始代数。
As you noticed, the definition of the functor for Rose a
is trickier due to the fact that the recursive occurrence of the type is fed into a List
. 正如您所注意到的,
Rose a
的仿函数的定义比较复杂,因为类型的递归出现被送入List
。 The problem is that List
is itself a recursive type obtained as a fixed point. 问题是
List
本身是一个作为固定点获得的递归类型。 List (Rose a)
basically corresponds to an "arbitrary number of elements of Rose a
", something that you cannot express with a signature of products and sums alone, hence the need for additional abstraction over these multiple recursive points. List (Rose a)
基本上对应于“ Rose a
任意数量的元素”,这是你无法用产品和总和的签名来表达的东西,因此需要对这些多个递归点进行额外的抽象。
A functor FA - : * -> *
will not work, as we would need to find something such that 仿函数
FA - : * -> *
将不起作用,因为我们需要找到这样的东西
F A X ≃ A × (1 + X × List X)
F A X ≃ A × (1 + X × (1 + X × List X))
F A X ≃ A × (1 + X × (1 + X × (1 + X × List X)))
...
One way to do it is to just treat List
as primitive. 一种方法是将
List
视为原语。 Then Rose a
is just the fixed point of 然后
Rose a
只是固定点
RoseF A : * -> * = λ X . A × List X
Another, more interesting way is to follow the suggestion in the reference you posted, and notice that the type of Rose a
can be generalized to abstract over the functor in which the recursive occurrence is fed into 另一个更有趣的方法是遵循您发布的引用中的建议,并注意
Rose a
的类型可以被推广为抽象的函数,其中递归事件被送入
GRose F A ≃ A × F (GRose F A)
now GRose
has type (* -> *) -> (* -> *)
, hence it is an higher order functor mapping an endofunctor into another one. 现在
GRose
有类型(* -> *) -> (* -> *)
,因此它是一个更高阶的函子,将endofunctor映射到另一个。 In our example, it would map the functor List
into the type of rose trees. 在我们的示例中,它会将仿函数
List
映射到玫瑰树的类型。
Notice however that GRose is still recursive, so the above is actually stating an isomorphism rather than a solution to our problem. 但请注意,GRose仍然是递归的,所以上面实际上是说明了同构而不是我们问题的解决方案。 We can try to fix (wink wink) this by additionally abstracting over the recursive point
我们可以尝试通过另外抽象递归点来修复(眨眼)
HRose G F A = A × F (G F A)
Notice that now HRose
is a regular higher-order functor of type ((* -> *) -> (* -> *)) -> (* -> *) -> (* -> *)
, hence it maps higher-order functors into higher-order functors. 请注意,现在
HRose
是一个常规的高阶函子,类型为((* -> *) -> (* -> *)) -> (* -> *) -> (* -> *)
,因此它映射得更高将函子编程到高阶函子中。 Computing the least fixed point of HRose
gives us 计算
HRose
的最小固定点给了我们
μ(HRose) F A ≃ A × F (μ(HRose) F A)
So if we put Rose ≡ μ(HRose) List
, we get 所以,如果我们把
Rose ≡ μ(HRose) List
,我们得到
Rose A ≃ A × List (Rose A)
which is exactly the defining equation for rose trees. 这正是玫瑰树的定义方程。 You can find many further examples of the theory and practice of generic programming using fixed points over higher-order functors.
您可以在高阶仿函数上找到使用固定点的泛型编程理论和实践的更多示例。 Here , for example, Bird and Paterson develop it in the context of nested datatypes (but the definitions clearly hold in general).
这里 ,例如,伯德和帕特森发展它在嵌套数据类型的情况下(但定义明确,一般持有)。 They also show the systematic construction of folds over datatypes defined in such way, as well as various laws.
它们还显示了以这种方式定义的数据类型的折叠的系统构造,以及各种定律。
You seem to understand how this is modelled 你似乎明白这是如何建模的
data List a = Nil | Cons a (List a)
by taking, for any given A
, the initial algebra of the endofunctor F(A, -) = 1 + A × (-)
. 对于任何给定的
A
,取内含子F(A, -) = 1 + A × (-)
的初始代数。 Let's call this initial algebra L(A)
. 我们称之为初始代数
L(A)
。
If we forget the morphism in L(A)
, we can sat that L(A)
is an object of our category. 如果我们忘记
L(A)
的态射,我们可以认为L(A)
是我们类别的对象。 Better, L(-)
is not only a mapping from objects to objects, but can be seen as an endofunctor. 更好的是,
L(-)
不仅是从对象到对象的映射,而且可以看作是一个endofunctor。
Once L
is seed as an endofunctor, the recursive type 一旦
L
是种子作为endofunctor,递归类型
data Rose a = Node a (List (Rose a))
is interpreted by taking, for any A
m the initial algebra of the functor 通过对于任何
A
m来解释仿函数的初始代数
G A = A * L A
which is a functor obtained by composing L
and *
(and the diagonal functor). 这是一个通过组合
L
和*
(和对角函子)得到的仿函数。 Hence, the same approach works. 因此,相同的方法有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.