假设我正在为F#中的特定于域的语言构建解析器。
我已经定义了一个有区别的联合来表示表达式:
type Expression =
| Equality of Expression*Expression
| NonEquality of Expression*Expression
| Or of Expression*Expression
| And of Expression*Expression
| If of Expression*Expression
| IfElse of Expression*Expression*Expression
| Bool of bool
| Variable of string
| StringLiteral of string
现在,我已经构建了一个Expression
类型的AST,并希望为它生成代码。
我有一个函数可以对表达式进行类型推断和类型检查。
它被定义为
let rec InferType expr =
match expr with
| Equality(e1,e2) -> CheckTypes (InferType e1) (InferType e2)
| Or(e1,e2) -> CheckTypes (InferType e1) (InferType e2)
| And(e1,e2) -> CheckTypes (InferType e1) (InferType e2)
...
我还有另一个函数来生成遵循类似模式的代码:获取表达式,为union中的每个项写出模式匹配语句。
我的问题是:这是用F#做的惯用方法吗?
在我看来,如果工会的每个成员在本地定义了自己的InferType
和GenerateCode
,那将会更清晰。
如果我使用C#,我将使用Expression
和InferType
的虚拟方法定义一个名为GenerateCode
的抽象基类,然后在每个子类中覆盖它们。
还有其他办法吗?
答案 0 :(得分:17)
在我看来,它会是 如果工会的每个成员都清洁 定义了自己的
InferType
和GenerateCode
在当地使用它。
我相信你的意思是“更熟悉”,而不是“更清洁”。
真的,您的理想是让代码生成器实现分布在10个不同的类中吗?
你是否想要“按类型”或“按操作”分组事物之间肯定存在根本的紧张关系。通常的OO方式是“按类型”,而FP(函数编程)方式是“按操作”。
对于编译器/解释器(或OO中大多数事情严重依赖于访问者模式),我认为“按操作”是更自然的分组。 If
和And
以及Or
的代码生成器可能有一些共同点;各节点的类型测试者同样具有共性;如果你制作漂亮的打印机,可能会有所有节点漂亮打印实现共同的格式化例程。相比之下,IfElse
的打印,类型检查和代码生成实际上根本没有多大关系,那么为什么要将这些组合在IfElse
类中呢?
(回答你的问题:是的,这是惯用的。还有另一种方式 - 是的,你可以像在C#中那样做。我想你会发现你对C#的方式不太满意,并且代码也将以这种方式大2-3倍,没有任何好处。)
答案 1 :(得分:9)
作为一名OCaml程序员,我认为这完全是惯用语。顺便提一下,与使用类方法编写类层次结构相比,这可以更好地分离关注点。你会在OO语言中使用InferType访问者获得类似的模块化,但代码会更多。
答案 2 :(得分:1)
您通常在函数式语言中执行的另一件事是在数据类型上定义fold
操作,然后根据折叠定义类型检查和代码生成函数。在这种特殊情况下,我不确定它会给你带来多大的好处,因为折叠函数会有很多参数,以至于它不会特别容易理解:
let rec fold eqE nonE andE orE ... = function
| Equality(e1,e2) -> eqE (e1 |> fold eqE nonE ...) (e2 |> fold eqE nonE ...)
| NonEquality(e1,e2) -> nonE ...
...
let inferTypes = fold checkTypes checkTypes checkTypes ...