繁体   English   中英

为F#中的小型AST选择是使用区分联合还是记录类型

[英]Choosing whether to use Discriminated Unions or Record Types for a small AST in F#

假设我正在实现一个非常简单的玩具语言解析器。 我正在决定是否使用DU或记录类型(也许是两者的混合?)。 该语言的结构为:

a Namespace consists of a name and a list of classes
a Class consists of a name and a list of methods
Method consists of a name, return type and a list of Arguments
Argument consists of a type and a name

用这种简单语言编写的程序示例:

namespace ns {
  class cls1 {
    void m1() {}
  }

  class cls2 {
    void m2(int i, string j) {}
  }
}

您将如何建模,为什么?

您几乎肯定要使用DU来实现交替,其中代码结构的任何部分都可能是多种可能性之一。 混合可能是理想的选择,尽管您可以使用元组代替记录-这可能使它更易于使用,但由于在元组中没有命名项,因此可能更难以阅读和维护。

我会像这样建模

type CompilationUnit = | Namespace list

and Namespace = { Name : String
                  Body : NamespaceBody }

and NamespaceBody = | Classes of Class list

and Class = { Name : String
              Body : ClassBody }

and ClassBody = | Members of Member list

and Member = | Method of Method

and Method = { Name : String
               Parameters : Parameter list option
               ReturnType : TypeName option
               Body : MethodBody }

and Parameter = { Name : String
                  Type : TypeName }

and MethodBody = ...

and TypeName = ...

在您的示例语言中,对DU的需求可能并不明显,但是只要您在代码中有任何一点(可能是一项或多项),就将很清楚。 举例来说,例如,如果您向班级添加字段-您只需要向Member添加新的Field区分。

如果您使用语法来解析语言(LL / LALR或类似语言),则对于语法中的每个替换规则,您可能都需要一个匹配的DU。

一个名称空间由一个名称和一个类列表组成;一个类由一个名称和一个方法列表组成;方法由一个名称,返回类型和一个参数列表组成;一个参数由一个类型和一个名称组成。语言:

您还需要一个用于类型系统的类型定义,而这实际上是联合类型有价值的唯一地方:

type Type = Void | Int | String

因此,您所用语言的类型可以是int或字符串或void,但不能为空(例如null),并且不能超过这些选项之一。

命名空间的类型可以完全是匿名的,如下所示:

string * (string * (Type * string * (Type * string) list) list) list

您可以这样定义示例名称空间:

"ns", ["cls1", [Void, "m1", []]
       "cls2", [Void, "m2", [Int, "i"; String, "j"]]]

在实践中,您可能希望能够将名称空间放在其他名称空间中,并将类放在类中,以便您可以将代码演变为如下所示:

type Type =
  | Void
  | Int
  | String
  | Class of Map<string, Type> * Map<string, Type * (Type * string) list>

type Namespace =
  | Namespace of string * Namespace list * Map<string, Type>

Namespace("ns", [],
          Map
            [ "cls1", Class(Map[], Map["m1", (Void, [])])
              "cls2", Class(Map[], Map["m2", (Void, [Int, "i"; String, "j"])])])

匿名类型很好,只要它们不会引起混乱。 根据经验,如果您有两个或三个字段并且它们是不同的类型(如此处的“方法”),则元组就可以了。 如果有更多字段或具有相同类型的多个字段,那么该切换到记录类型了。

因此,在这种情况下,您可能要为方法引入记录类型:

type Method =
  { ReturnType: Type
    Arguments: (Type * string) list }

and Type =
  | Void
  | Int
  | String
  | Class of Map<string, Type> * Map<string, Method>

type Namespace =
  | Namespace of string * Namespace list * Map<string, Type>

Namespace("ns", [],
          Map
            [ "cls1", Class(Map[], Map["m1", { ReturnType = Void; Arguments = [] }])
              "cls2", Class(Map[], Map["m2", { ReturnType = Void; Arguments = [Int, "i"; String, "j"] }])])

也许还有一个辅助函数来构造这些记录:

let Method retTy name args =
  name, { ReturnType = retTy; Arguments = args }

Namespace("ns", [],
          Map
            [ "cls1", Class(Map[], Map[Method Void "m1" []])
              "cls2", Class(Map[], Map[Method Void "m2" [Int, "i"; String, "j"]])])

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM