F#设计模式

时间:2009-12-20 17:57:32

标签: f#

假设我正在为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#做的惯用方法吗?

在我看来,如果工会的每个成员在本地定义了自己的InferTypeGenerateCode,那将会更清晰。

如果我使用C#,我将使用ExpressionInferType的虚拟方法定义一个名为GenerateCode的抽象基类,然后在每个子类中覆盖它们。

还有其他办法吗?

3 个答案:

答案 0 :(得分:17)

  

在我看来,它会是   如果工会的每个成员都清洁   定义了自己的InferType和   GenerateCode在当地使用它。

我相信你的意思是“更熟悉”,而不是“更清洁”。

真的,您的理想是让代码生成器实现分布在10个不同的类中吗?

你是否想要“按类型”或“按操作”分组事物之间肯定存在根本的紧张关系。通常的OO方式是“按类型”,而FP(函数编程)方式是“按操作”。

对于编译器/解释器(或OO中大多数事情严重依赖于访问者模式),我认为“按操作”是更自然的分组。 IfAnd以及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 ...