简体   繁体   English

F#设计模式

[英]F# design pattern

Lets say I'm building a parser for a domain-specific language in F#. 假设我正在为F#中的特定于域的语言构建解析器。

I've defined a discriminated union to represent expressions: 我已经定义了一个区分联合来表示表达式:

    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

Now, I've built up an AST of type Expression and want to generate code for it. 现在,我已经建立了一个Expression类型的AST,并希望为它生成代码。 I have one function which does type inference and type checking on an expression. 我有一个函数,它对表达式进行类型推断和类型检查。

It's defined like 它被定义为

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

And I have another function to generate code which follows a similar pattern: Take an expression, write pattern-matching statements for each item in the union. 我还有另一个函数来生成遵循类似模式的代码:获取表达式,为union中的每个项写入模式匹配语句。

My question is: Is this the idiomatic way to do it in F#? 我的问题是:这是用F#做的惯用方式吗?

It seems to me that it would be cleaner if each member of the union defined its own InferType and GenerateCode locally with it. 在我看来,如果联盟的每个成员在本地定义了自己的InferTypeGenerateCode ,它会更清晰。

If I were using C#, I would define some abstract base class called Expression with virtual methods for InferType and GenerateCode and then override them in each subclass. 如果我使用C#,我将使用InferTypeGenerateCode虚方法定义一个名为Expression抽象基类,然后在每个子类中覆盖它们。

Is there any other way to do this? 有没有其他方法可以做到这一点?

It seems to me that it would be cleaner if each member of the union defined its own InferType and GenerateCode locally with it. 在我看来,如果联盟的每个成员在本地定义了自己的InferTypeGenerateCode ,它会更清晰。

I believe you mean "more familiar", not "cleaner". 我相信你的意思是“更熟悉”,而不是“更清洁”。

Really, is your ideal to have you code generator implementation spread out across 10 different classes? 真的,您的理想是让代码生成器实现分布在10个不同的类中吗?

There is definitely a fundamental tension between whether you want to group things "by type" or "by operation". 在你是否想要“按类型”或“按操作”分组事物之间肯定存在根本的紧张关系。 The usual OO way is "by type" whereas the FP (functional programming) way is "by operation". 通常的OO方式是“按类型”,而FP(函数编程)方式是“按操作”。

In the case of a compiler/interpreter (or most things that in OO rely heavily on the Visitor pattern), I think "by operation" is the more natural grouping. 在编译器/解释器的情况下(或OO中的大多数事情严重依赖于访问者模式),我认为“通过操作”是更自然的分组。 The code generator for If and And and Or may have a bit in common; IfAndOr的代码生成器可能有一点共同之处; the typecheckers of the various nodes will similarly have commonalities; 各节点的类型测试者同样具有共性; if you make a pretty-printer, there will likely be formatting routines common to all the node-pretty-printing implementations. 如果你制作漂亮的打印机,可能会有所有节点漂亮打印实现共同的格式化例程。 In contrast, printing, typechecking, and codegenning an IfElse really don't have much to do with one another at all, so why would you want to group those in an IfElse class? 相比之下,IfElse的打印,类型检查和代码IfElse实际上根本没有多大关系,那么为什么要将它们分组到IfElse类中呢?

(To answer your questions: yes, this is idiomatic. Is there another way - yes, you can do it just like you would in C#. I think you'll find you're much less happy with the C# way, and that the code will also be 2-3x larger that way, with no benefit.) (回答你的问题:是的,这是惯用的。还有另一种方式 - 是的,你可以像在C#中那样做。我想你会发现你对C#的方式不太满意,而且这样的代码也将大2-3倍,没有任何好处。)

As an OCaml programmer, I'd say this is entirely idiomatic. 作为OCaml程序员,我认为这完全不是惯用语。 Incidentally, this gives you better separation of concerns than if you had written a class hierarchy with class methods. 顺便提一下,与使用类方法编写类层次结构相比,这可以更好地分离关注点。 You'd get similar modularity in an OO language with an InferType visitor, but it would be a lot more code. 你会在OO语言中使用InferType访问者获得类似的模块化,但它会有更多的代码。

The other thing that you might typically do in a functional language is to define a fold operation on the datatype and then define the typechecking and code generating functions in terms of the fold. 您通常在函数式语言中执行的另一件事是在数据类型上定义fold操作,然后根据fold定义类型检查和代码生成函数。 In this particular case I'm not sure that it buys you much because the fold function would have so many arguments that it won't be particularly easy to comprehend: 在这种特殊情况下,我不确定它会给你带来多大的好处,因为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 ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM