Haskell 如何在没有嵌套列表的情况下编写与树同构的类型?
在Haskell中,据说任何ADT都可以表示为乘积之和。我试图在Haskell 如何在没有嵌套列表的情况下编写与树同构的类型?,haskell,functional-programming,algebraic-data-types,Haskell,Functional Programming,Algebraic Data Types,在Haskell中,据说任何ADT都可以表示为乘积之和。我试图在Data.Tree上找到一个与Tree同构的平面类型 Tree a = Node a [Tree a] -- has a nested type (List!) 我想为没有嵌套类型的树编写功能相同的定义: Tree = ??? -- no nested types allowed 为此,我尝试为类型代数编写递归关系: L a = 1 + a * L a T a = a * L (T a) 内联线L,我有: T a = a *
Data.Tree
上找到一个与Tree
同构的平面类型
Tree a = Node a [Tree a] -- has a nested type (List!)
我想为没有嵌套类型的树编写功能相同的定义:
Tree = ??? -- no nested types allowed
为此,我尝试为类型代数编写递归关系:
L a = 1 + a * L a
T a = a * L (T a)
内联线L,我有:
T a = a * (1 + T a * L (T a))
T a = a * (1 * T a * (1 + T a * L (T a)))
T a = a * (1 + T a * (1 + T a * (1 + T a * L (T a))))
那是行不通的,所以我停下来做乘法,留下:
T a = a + a * T a + a * T a * T a ...
这与:
T a = a * (T a) ^ 0 + a * (T a) ^ 1 + a * (T a) ^ 2 ...
这是一个乘积之和,但它是无限的。我不能用哈斯克尔的话写。滥用代数:
(T a) - (T a) ^ 2 = a * T a
- (T a) ^ 2 - a * T a + (T a) = 0
解决ta
,我发现:
T a = 1 - a
这显然毫无意义。那么,回到原来的问题:如何从Data.Tree
展平Tree
,这样我就可以编写一个与之同构的类型,而不需要嵌套类型
这个问题不是重复的。最后一个是关于用Scott编码表示嵌套类型,正确答案是“忽略嵌套”。这一部分继续询问如何展平嵌套类型(因为它是Scott编码的特定用途所必需的,但通常不是强制性的)
T a = a * (1 + T a * L (T a))
你可以继续
= a + a * T a * L (T a) -- distribute
= a + T a * (a * L (T a)) -- commute and reassociate
= a + T a * T a -- using your original definition of T backwards
那么你到了
data Tree a = Leaf a | InsertLeftmostSubtree (Tree a) (Tree a)
然而,我不确定这在多大程度上是一个普通程序的例子。在阅读任何答案之前,我认为这是一个有趣的谜题,并得出了与公认答案相当的答案:
data Tree' a = Node a [Tree' a] deriving (Show, Eq, Ord)
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show, Eq, Ord)
除此之外,我还以似乎唯一可能的方式编写了转换函数:
convert :: Tree' a -> Tree a
convert (Node x []) = (Leaf x)
convert (Node x (t:ts)) = Branch (convert t) $ convert (Node x ts)
convert' :: Tree a -> Tree' a
convert' (Leaf x) = Node x []
convert' (Branch t ts) = Node x $ convert' t : subtrees
where (Node x subtrees) = convert' ts
这些函数的实现并不是正确性的证明,但它们的类型检查、没有非穷举模式匹配,并且似乎适用于简单输入(即convert.convert'==id
),这有助于表明数据类型彼此同构,这是令人鼓舞的
至于如何构建这样一个东西的一般结构,我的理解与公认答案中的类型代数不同,因此我的思维过程可能有助于推导出一个通用方法。主要的是注意到,[x]
可以按照通常的方式转换为数据列表a=Nil | Cons a(列表a)
。因此,我们需要将该转换应用于[Tree'a]
字段,同时还保留[Tree'a]
上方的额外a
。因此,我的叶a
自然地与Nil
等价,但有一个额外的a
;然后分支
构造函数类似于Cons b(列表b)
我认为您可以对任何包含列表的数据构造函数执行类似的操作:给定构造函数ab[c]
,将其转换为新类型中的两个构造函数:
data Converted a b c = Nil a b | Cons c (Converted a b c)
如果旧构造函数中有两个列表,则可以为每个列表指定一个构造函数,只需将单个项添加到其中一个列表中:
data Old a b c = Old a [b] [c]
data New a b c = Done a
| AddB b (New a b c)
| AddC c (New a b c)
虽然我想我知道你想要什么(也许里德已经解决了),你能在一个平面数据结构下添加你所理解的吗?(因为你似乎不介意递归,这让我感到惊讶)具体地说,我希望它可以表示为一个乘积的和,是的,可能是,在绑定变量上递归。例如:
data Rec a=aa(Rec a)| ba | ca(Rec a)a | da
,完全有效。但是在另一个递归过程(比如这里的[treea]
)上进行递归是不行的。我之所以要这样做,是因为我正在使用Scott编码在Lambda演算上对Haskell数据类型进行编码,而Scott编码不考虑嵌套类型,因此我必须在不嵌套的情况下进行编码。@ViclibTree
对于Scott编码来说似乎没有问题。不应该工作吗?安德拉斯,当你试图用规范化术语来折叠它们时就不行了,就像你在另一个线程中证明的那样。为此,我必须用递归调用扩展编码,这样cons就变成了(head-tail-cons-nil)→ cons cons nil头尾)
而不是(头尾cons nil→ cons(头尾)
。通过使用它,我可以通过将任何数据类型表示为Scott:[[Field]]]
,从而为任何数据类型导出一个规范化的折叠,其中(Field::Bool)==true,如果字段是递归的
。例如,通过这种方式,列表被表示为[[false-true][]]
。我不确定这是唯一的办法。有什么想法吗?很好,谢谢。虽然它显然解决了特定的问题,但它让我感到好奇,因此,如果有人愿意,我很高兴听到为什么这是可行的,如果这总是可以做到:)非常感谢你解释你的思维过程,这是非常有洞察力的,对我所做的事情帮助很大。