Tree 带1个构造函数的SML多类型二叉树

Tree 带1个构造函数的SML多类型二叉树,tree,binary-tree,sml,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)); &

我正在尝试实现一个二叉树,其中每个节点都可以保存“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".
也许语法不正确,或者我的想法是错误的

一种二叉树,其中每个节点都可以保存“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,字符串为tree

当用一个值作为前缀时,它采用类型为“b”的值,而用另一个值作为前缀时,它采用类型为“c”的值。请注意,它们可能在这里被命名为“a”和“b”,但我认为给它们新的变量名可以减少用“b”和“c”替换“a”时的混淆

另外请注意,通常这种'b',c任一类型都有名为Left和Right的构造函数,但我改变了这一点,因为Left和Right对二叉树也有意义,这可能会增加混淆。树的方向仍然由位置决定,因此第一棵“a树”是左子树,第二棵“a树”是右子树

您可以将这两种数据类型组合成一个定义,如下所示:

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,因为它结合了两个概念。 以前,树类型构造函数只接受“a”类型参数。现在eithertree接受'a',b,因为-a-和-b之间的选择机制已经嵌入到树类型中。 值构造函数更容易一些:BranchA自然地假设它的第一个参数是“a”,其中Branch每次都必须显式地在“a”前面有一个参数,对于“b”和其他参数也是如此。 数据类型定义更复杂一些:现在对子树的每个自引用都更复杂,因为它需要一对类型参数“a”、“b”,而不是像第一个“a树”那样只需要一个类型参数。我认为,当必须定义数据类型时,这主要是一个缺点。这可能就是你陷入困境的原因。 “a,”b eithertree的可组合性较差:假设您构建了一组二叉树遍历函数。这些在树上不起作用,因为它们不是树。但是一棵“a”,b或者是一棵“a”是“a”,b或者是“a”的树。因此,您可以重用更少的代码。 一种二叉树,其中每个节点都可以保存“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,字符串为tree

当用一个值作为前缀时,它采用类型为“b”的值,而用另一个值作为前缀时,它采用类型为“c”的值。请注意,它们可能在这里被命名为“a”和“b”,但我认为给它们新的变量名可以减少用“b”和“c”替换“a”时的混淆

另外请注意,通常这种'b',c任一类型都有名为Left和Right的构造函数,但我改变了这一点,因为Left和Right对二叉树也有意义,这可能会增加混淆。树的方向仍然由位置决定,因此第一棵“a树”是左子树,第二棵“a树”是右子树

您可以将这两种数据类型组合成一个定义,如下所示:

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,因为它结合了两个概念。 以前,树类型构造函数只接受“a”类型参数。现在eithertree接受'a',b,因为-a-和-b之间的选择机制已经嵌入到树类型中。 值构造函数更容易一些:BranchA自然地假设它的第一个参数是“a”,其中Branch每次都必须显式地在“a”前面有一个参数,对于“b”和其他参数也是如此。 数据类型 e定义更复杂一些:现在对子树的每个自引用都更复杂,因为它需要一对类型参数“a”、“b”,而不是像第一个“a树”那样只需要一个类型参数。我认为,当必须定义数据类型时,这主要是一个缺点。这可能就是你陷入困境的原因。 “a,”b eithertree的可组合性较差:假设您构建了一组二叉树遍历函数。这些在树上不起作用,因为它们不是树。但是一棵“a”,b或者是一棵“a”是“a”,b或者是“a”的树。因此,您可以重用更少的代码。 你很接近

因为您的联合类型是本地的,所以在'a',b树的定义之外根本不能使用它

这一问题很容易解决——让它不是本地的:

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 Basis库中

第二个问题是,您需要使用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是不同的类型。

你们非常接近

因为您的联合类型是本地的,所以在'a',b树的定义之外根本不能使用它

这一问题很容易解决——让它不是本地的:

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 Basis库中

第二个问题是,您需要使用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是不同的类型

非叶节点的构造函数应该是单数的,并且没有来自用户的附加信息,比如他想要构建树的值的类型。我认为我的结果示例解释了这一点

我将提供另一个答案,因为现在给出的两个答案在这个约束方面都不令人满意。正如我所评论的,如果您想要一个接受int或字符串的单个构造函数,那么您需要的是即席多态性而不是参数多态性

这个OCaml问题有一些相似之处:-主要区别在于这个问题需要一个函数,而这个问题需要一个数据类型定义。正如Jeffrey Scofield指出的,在您的案例中,使类型适应int和string:

int和string的唯一常见类型是“a”,即任何类型。由于该值可以是任何值,因此没有可以应用于它的特定操作。因此,没有直接的方法来编写所需的函数

但正如我所回答的,OCaml有一个名为modular implicits的实验性扩展,它允许您编写将模块作为参数的函数,在这些模块中,您可以提供一个类型参数,以及两种或更多类型中通用的函数接口。我不知道标准ML有类似的情况

您所要求的可以归类为a,在Haskell中,您可以使用存在量化或GADTs GHC扩展。通常情况下,Haskell的一些重载机制在ML模块系统中有类似的用途,但在GADTs中,我能找到的最好的方法是,因此这更多的是概念证明,而不是实际解决方案

非叶节点的构造函数应该是单数的,并且没有来自用户的附加信息,比如他想要构建树的值的类型。我认为我的结果示例解释了这一点

我将提供另一个答案,因为现在给出的两个答案在这个约束方面都不令人满意。正如我所评论的,如果您想要一个接受int或字符串的单个构造函数,那么您需要的是即席多态性而不是参数多态性

这个OCaml问题有一些相似之处:-主要区别在于这个问题需要一个函数,而这个问题需要一个数据类型定义。正如Jeffrey Scofield指出的,在您的案例中,使类型适应int和string:

int和string的唯一常见类型是“a”,即任何类型。由于该值可以是任何值,因此没有可以应用于它的特定操作。因此,没有直接的方法来编写所需的函数

但正如我所回答的,OCaml有一个名为modular implicits的实验性扩展,它允许您编写将模块作为参数的函数,在这些模块中,您可以提供一个类型参数,以及两种或更多类型中通用的函数接口。我不知道标准ML有类似的情况

你要的东西可以归类为a,在Haskell你会得到a
具有存在量化或GADTs GHC扩展的t。通常情况下,Haskell的一些重载机制在ML模块系统中有类似的用途,但在GADT的情况下,我能找到的最好的方法是,因此这更像是一个概念证明,而不是一个实用的解决方案。

难道没有一种方法可以定义一个多类型树而不用用户猜测类型吗?我需要它像我在我的问题中要求的那样,1个构造函数,用于2种类型:Br100,Lf,Brhello,Lf,Lf;还有,为什么模式匹配在这种情况下不起作用?@codecavader:但是您的示例,Br100,Lf,Brhello,Lf,Lf使用了多个构造函数:Br是非空树的构造函数,Lf是空树的构造函数,100是整数值的构造函数,hello是字符串值的构造函数。为了使Br构造函数能够接受不同的类型作为其第一个参数,您在的领域中,而不是“a”是类型参数。可能我说错了。我知道树有2个构造器,我的意思是非叶节点的构造器应该是单数的,并且没有来自用户的附加信息,比如他想要构建树的值的类型。我想我的结果示例解释了这一点。@Code囤积者需要猜测的不是用户,而是语言。SML不知道你作为一个用户是想要一个int,string u还是string,int u的树,除非你告诉它你想要什么。这样的判别式联盟不是C或C++中的联合类型,没有一种方法来定义一个多类型的树,而不需要用户猜测类型。我需要它像我在我的问题中要求的那样,1个构造函数,用于2种类型:Br100,Lf,Brhello,Lf,Lf;还有,为什么模式匹配在这种情况下不起作用?@codecavader:但是您的示例,Br100,Lf,Brhello,Lf,Lf使用了多个构造函数:Br是非空树的构造函数,Lf是空树的构造函数,100是整数值的构造函数,hello是字符串值的构造函数。为了使Br构造函数能够接受不同的类型作为其第一个参数,您在的领域中,而不是“a”是类型参数。可能我说错了。我知道树有2个构造器,我的意思是非叶节点的构造器应该是单数的,并且没有来自用户的附加信息,比如他想要构建树的值的类型。我想我的结果示例解释了这一点。@Code囤积者需要猜测的不是用户,而是语言。SML不知道你作为一个用户是想要一个int,string u还是string,int u的树,除非你告诉它你想要什么。这样的判别式联盟不像C或C++中的联合类型。