[英]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.