【问题标题】:SML multiple type binary tree with 1 constructor具有 1 个构造函数的 SML 多类型二叉树
【发布时间】:2020-04-09 19:20:22
【问题描述】:

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

可能是语法不对,还是我的想法不对?

【问题讨论】:

    标签: tree binary-tree sml


    【解决方案1】:

    你们很亲密。

    由于你的联合类型是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 是不同的类型。)

    【讨论】:

    • 有没有一种方法可以定义多类型树而无需用户猜测类型?我需要它就像我在我的问题中要求的那样,2 种类型的 1 个构造函数:Br(100,Lf,Br("hello",Lf,Lf));另外,为什么在这种情况下模式匹配不起作用?
    • @CodeHoarder:但是你的例子,Br(100,Lf,Br("hello",Lf,Lf)) 使用了多个构造函数:Br 是非空树的构造函数,Lf 是空树的构造函数,@987654338 @ 是整数值的构造函数,"hello" 是字符串值的构造函数。为了使Br 构造函数能够接受不同类型作为其第一个参数,您处于ad-hoc polymorphism 而不是parametric polymorphism 的领域(其中'a 是类型参数)。
    • 也许我说错了。我知道树有 2 个构造函数,我的意思是非叶节点的构造函数应该是单数的,并且没有来自用户的附加信息,比如他想要构建树的值的类型。我认为我的结果示例解释了它。
    • @CodeHoarder 需要猜测的不是用户,而是语言。 SML 无法知道您作为用户是否想要(int, string) u(string, int) u 的树,除非您告诉它您想要什么。 (像这样的可区分联合与 C 或 C++ 中的联合类型不同。)
    【解决方案2】:

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

    我将提供另一个答案,因为现在给出的两个答案都不能令人满意。正如我所评论的,如果您想要一个采用 intstring 的单个构造函数,您需要 ad-hoc polymorphism 而不是 参数多态性

    与这个 OCaml 问题有一些相似之处:Make OCaml function polymorphic for int lists and float lists——主要区别在于该问题需要一个函数,而该问题需要一个数据类型定义。正如 Jeffrey Scofield 指出的那样(在您的情况下将类型调整为 intstring):

    intstring 的唯一常见类型是 'a,即任何类型。由于该值可以是任何值,因此您无法对其应用特定操作。所以没有直接的方法来编写你想要的函数。

    但正如我也回答的那样,OCaml 有一个名为“模块化隐式”的实验性扩展,它允许您编写将模块作为参数的函数,并且在这些模块中,您可以提供类型参数,以及一个常见的函数接口两种或多种类型。我不知道标准 ML 有类似的东西。

    您所要求的可以归类为heterogenous tree,而在Haskell 中,您可以使用ExistentialQuantificationGADTs GHC 扩展来实现。通常情况下,Haskell 的一些重载机制在 ML 模块系统中有一些类似的用途,但在 GADT 的情况下,我能找到的最好的是encoding of GADTs in the ML module system using Church numerals,所以这更像是一个证明——概念上的,而不是实际的解决方案。

    【讨论】:

      【解决方案3】:

      一棵二叉树,其中每个节点都可以保存类型'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 的构造函数,但我更改了它,因为“左”和“右”也对二叉树有意义,这可能会添加混淆。树的方向还是由位置决定的,所以第一个'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 每次都必须在One 前面显式地有One(同样对于@ 987654351@ 和 Other)。
      • 数据类型定义有点复杂:每个对子树的自引用现在都更加复杂,因为它需要一对类型参数('a, 'b),而不仅仅是像第一个'a tree那样的单个类型参数做过。我认为,这主要是必须定义数据类型时的一个缺点。这可能就是你被卡住的原因。
      • ('a, 'b) eithertree 的组合性较差:假设您构建了一堆二叉树遍历函数。这些不适用于('a, 'b) eithertree,因为它们不是'a trees。但是('a, 'b) either tree 'a tree'a('a, 'b) either。因此,您可以重用更少的代码。

      【讨论】:

        猜你喜欢
        • 2011-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多