我正在尝试实现一个二叉树,其中每个节点都可以保存'a或'b类型的信息。简单的解决方案是使用2个这样的构造函数:
datatype ('a, 'b) Tree = Lf
| Br1 of 'a * (('a, 'b) Tree) * (('a, 'b) Tree)
| Br2 of 'b * (('a, 'b) Tree) * (('a, 'b) Tree);
Br1(100,Lf,Br2("hello",Lf,Lf));
>val it = Br1 (100, Lf, Br2 ("hello", Lf, Lf)): (int, string) Tree;
但是,我想使用1个构造函数,以便结果如下:
Br(100,Lf,Br("hello",Lf,Lf));
>val it = Br (100, Lf, Br ("hello", Lf, Lf)): (int, string) Tree;
模式匹配似乎不起作用,它在调用Br时返回长类型冲突错误:
datatype ('a, 'b) Tree = Lf
| Br of 'a * (('a, 'b) Tree) * (('a, 'b) Tree)
| Br of 'b * (('a, 'b) Tree) * (('a, 'b) Tree);
我感觉到它与并集数据类型有关,所以我尝试了以下操作,但是当我尝试这样调用Br时,会出现错误:
local
datatype ('a,'b) u = t1 of 'a
| t2 of 'b;
in
datatype ('a, 'b) Tree = Lf
| Br of ('a,'b) u * (('a, 'b) Tree) * (('a, 'b) Tree);
end;
Br(100,Lf,Br("hello",Lf,Lf));
Elaboration failed: Unbound type "u".
也许语法不正确,或者我的想法是错误的?
答案 0 :(得分:2)
您非常亲密。
由于联合类型为local
,因此无法在('a, 'b) Tree
定义之外使用它。
此问题很容易解决-使其不在本地:
datatype ('a,'b) u = t1 of 'a
| t2 of 'b;
datatype ('a, 'b) Tree = Lf
| Br of ('a,'b) u * (('a, 'b) Tree) * (('a, 'b) Tree);
(u
类型通常非常有用,通常被称为“或者”,有时也称为“变量”。我不知道为什么它不在SML基础库中。)
第二个问题是,您需要使用u
的构造函数来创建u
的值,就像其他地方一样:
- Br(t1 100,Lf,Br(t2 "hello",Lf,Lf));
val it = Br (t1 100,Lf,Br (t2 #,Lf,Lf)) : (int,string) Tree
无法避免显式构造值。
(不可能有人猜测int
是t1
还是t2
类型; (int, string) u
和(string, int) u
是不同类型。)
答案 1 :(得分:1)
一个二叉树,其中每个节点都可以保存'a或'b
类型的信息
虽然您可以使用单个二叉树类型执行此操作,但是我将其分为两种数据类型:树类型和“ 'a
或'b
类型”,因为这两种是规范的数据类型,这意味着它们可以被功能性程序员识别。
datatype 'a tree = Leaf | Branch of 'a * 'a tree * 'a tree
datatype ('b, 'c) either = One of 'b | Other of 'c
val someTree = Branch (One 100, Leaf, Branch (Other "hello", Leaf, Leaf))
此树的类型为(int, string) either tree
。
使用One
前缀值时,其类型为'b
;使用Other
前缀值时,其类型为'c
。请注意,在这里可以将它们分别命名为'a
和'b
,但是我认为给它们新的变量名可以减少用'a'
代替('b, 'c) either
时的困惑。
(还请注意,通常,此('b, 'c) either
类型具有名为Left
和Right
的构造函数,但由于“ left”和“ right”也具有二进制含义,因此我对其进行了更改树,这可能会增加混乱。树的方向仍然由位置决定,因此第一个'a tree
是左子树,第二个'a tree
是右子树。)
您可以将这两种数据类型组合为一个定义,如下所示:
datatype ('a, 'b) eithertree =
Leaf
| BranchA of 'a * ('a, 'b) eithertree * ('a, 'b) eithertree
| BranchB of 'b * ('a, 'b) eithertree * ('a, 'b) eithertree
val anotherTree = BranchA (100, Leaf, BranchB ("hello", Leaf, Leaf))
一些注意事项:
eithertree
,因为它结合了两个概念。tree
类型的构造函数仅采用'a
类型的参数。现在eithertree
采用('a, 'b)
是因为'a
和'b
之间的选择机制已嵌入到树类型中。 BranchA
自然地假定其第一个参数是'a
,其中Branch
必须在{{ 1}}(每次One
和'a
同样)。'b
,而不仅仅是像第一个{ {1}}做到了。我认为这在必须定义数据类型时主要是一个缺点。这可能就是您卡住的原因。Other
的可组合性较差:假设您构建了一堆二叉树遍历函数。这些不适用于('a, 'b)
,因为它们不是'a tree
。但是('a, 'b) eithertree
是是('a, 'b) eithertree
,其中'a tree
是('a, 'b) either tree
。这样您就可以重复使用更少的代码。答案 2 :(得分:0)
非叶节点的构造函数应为单数,并且不应添加来自用户的任何信息,例如他要用于构建树的值的类型。我以为我的结果示例对此做了解释。
我将提供另一个答案,因为就此约束而言,现在给出的两个答案都不令人满意。正如我所评论的,如果您想要一个采用 int 或 string 的单一构造函数,则需要 ad hoc多态性而不是参数多态性。
此OCaml问题有一些相似之处:Make OCaml function polymorphic for int lists and float lists-主要区别是该问题需要一个函数,而此问题需要一个数据类型定义。正如Jeffrey Scofield指出的那样(针对您的情况,将类型调整为 int 和 string ):
int
和string
的唯一常见类型是'a
,即任何类型。由于该值可以是任何值,因此没有可以应用的特定操作。因此,没有简单的方法可以编写所需的函数。
但是我也要回答,OCaml有一个实验性扩展,称为“模块化隐式”,它使您可以编写以模块为参数的函数,并且在这些模块中,您可以提供类型参数以及函数接口,这些函数接口在两种或多种类型。我不知道标准ML的类似之处。
您要的内容可以归类为heterogenous tree,在Haskell中,您可以使用ExistentialQuantification
或GADTs
GHC扩展名之一。通常,Haskell的某些重载机制在ML模块系统中具有可比的用途,但是对于GADT,我能找到的最好的是encoding of GADTs in the ML module system using Church numerals,所以这更多的是概念验证而非实际解决方案。