具有1个构造函数的SML多类型二叉树

时间:2019-12-17 09:58:28

标签: tree binary-tree sml

我正在尝试实现一个二叉树,其中每个节点都可以保存'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".

也许语法不正确,或者我的想法是错误的?

3 个答案:

答案 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

无法避免显式构造值。
(不可能有人猜测intt1还是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类型具有名为LeftRight的构造函数,但由于“ 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 ):

  

intstring的唯一常见类型是'a,即任何类型。由于该值可以是任何值,因此没有可以应用的特定操作。因此,没有简单的方法可以编写所需的函数。

但是我也要回答,OCaml有一个实验性扩展,称为“模块化隐式”,它使您可以编写以模块为参数的函数,并且在这些模块中,您可以提供类型参数以及函数接口,这些函数接口在两种或多种类型。我不知道标准ML的类似之处。

您要的内容可以归类为heterogenous tree,在Haskell中,您可以使用ExistentialQuantificationGADTs GHC扩展名之一。通常,Haskell的某些重载机制在ML模块系统中具有可比的用途,但是对于GADT,我能找到的最好的是encoding of GADTs in the ML module system using Church numerals,所以这更多的是概念验证而非实际解决方案。