2

假设我正在实现一个非常简单的玩具语言解析器。我正在决定是使用 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) {}
  }
}

您将如何对此进行建模,为什么?

4

2 回答 2

6

您几乎肯定希望使用 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,但是一旦您在代码中有任何可能是一个或多个项目的点,就会变得清晰。比如说,如果你在你的类中添加字段 - 你只需要FieldMember.

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

于 2011-08-24T23:54:54.673 回答
0

命名空间由名称和类列表组成 类由名称和方法列表组成 方法由名称、返回类型和参数列表组成 参数由类型和名称组成 这个简单的程序示例语:

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

type Type = Void | Int | String

因此,您的语言中的类型可以是 int 或 string 或 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"]])])
于 2013-06-27T09:33:12.900 回答