F#设计模式

8
假设我正在使用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)
        ...

我还有另一个函数可以生成遵循相似模式的代码:接收一个表达式,在联合中的每个项上编写模式匹配语句。

我的问题是:这是在 F# 中惯用的方法吗?

我认为,如果联合的每个成员本地定义自己的 InferTypeGenerateCode,代码会更加简洁。

如果我使用 C#,我会定义一个名为 Expression 的抽象基类,其中包含虚方法 InferTypeGenerateCode,然后在每个子类中重写它们。

还有其他方法可以实现吗?

3个回答

18
在我看来,如果每个工会成员在其本地定义自己的InferTypeGenerateCode会更加熟悉。
我认为你的意思是“更熟悉”,而不是“更清洁”。
实际上,您期望您的代码生成器实现分散在10个不同的类中吗?
是否将事物按“类型”或“操作”进行分组存在根本性的紧张关系。通常的OO方式是“按类型”,而FP(函数式编程)方式是“按操作”。
在编译器/解释器(或大多数依赖于访问者模式的OO)的情况下,我认为“按操作”是更自然的分组方式。 IfAndOr的代码生成器可能有一些共同点;各种节点的类型检查器也将具有共性;如果制作漂亮的打印机,则所有节点漂亮的打印实现都可能有常见的格式化例程。相比之下,打印,类型检查和代码生成IfElse实际上根本没有多少联系,那么为什么要将这些内容分组在IfElse类中呢?
(回答您的问题:是惯用语。还有另一种方法-是的,您可以像在C#中一样做。我认为您会发现,以C#方式做的时候,您会感到不太满意,并且代码也会比这种方式大2-3倍,没有任何好处。)

1
谢谢 - 这就是我在寻找的答案。 - HS.

9
作为OCaml程序员,我可以说这是完全符合惯用法的。顺便说一下,这比你编写一个类层次结构和类方法要更好地分离关注点。在面向对象的语言中,您可以使用InferType访问者获得类似的模块化,但需要编写更多的代码。

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 ...

1
那个 fold 是构建访问者的一种更丑陋的方式吗?我仍然更喜欢原帖作者的风格,其中 inferTypes 是由一个模式匹配语句直接定义的。 - Tobu
2
@Tobu:我个人认为它比访问者模式要好看得多。就像我说的,在这个特定的例子中,有很多不同的情况,我认为用fold来定义事物并不更好。然而,通常情况下,在递归数据类型上定义折叠操作是有意义的(例如F#内置的List.fold)。 - kvb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接