F#:组织类型和模块的最佳实践

时间:2017-04-24 06:37:37

标签: types module f# naming

如果我采用定义类型的函数方法,然后描述对该类型进行操作的函数(而不是实例方法),我应该如何组织我的代码?

我通常采用以下三种方式之一:

(1):

module MyType =
    type MyType (x : int) =
        member val X = x with get

    let myFunction myType y = myType.X + y

(2):

type MyType (x : int) =
    member val X = x with get

[<RequireQualifiedAccess>]
module MyTypeOps =
    let myFunction myType y = myType.X + y

(3):

type MyType =
    {
        x : int
    }

    static member MyFunction myType y = myType.x + y

Pro(1)是函数在模块中定义。 (1)的缺点是模型中也定义了类型,导致实例化时出现MyType.MyType冗余,如果要允许[<RequireQualifiedAccess>]作为工作,则无法open MyType这种冗余的方法。

(2)的优点是模块中定义的函数,没有module.type冗余。 Con是模块不能与类型名称相同。

Pro(3)的方法是方法是静态的,没有module.type冗余。缺点是您无法在此方法下定义某些类型(例如非方法值和活动模式)。

通常情况下,我更喜欢(2),尽管我讨厌将模块命名为一些不那么具有描述性和直观性的模块。 (2)还允许我在极少数情况下使用type ... and ...创建相互依赖的类型,这对于在方法(1)等单独模块中定义的类型来说显然是不可能的。

我的F#程序员采用什么方法?我想知道我是否忽略了一些显而易见的东西(或者不那么明显),或者,如果我不是,那么是否存在一个约定无法将模块命名为与其相应类型相同的名称相同的命名空间。

1 个答案:

答案 0 :(得分:12)

您还没有列出第四种方法,即将类型和模块命名为相同。你认为它无法完成,但它实际上是一种非常普遍的做法;您只需(在4.1之前的F#版本中)使用模块上的[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]属性。在任何地方写这个都有点难看,这就是F# 4.1 made this the default behavior如果你定义一个与模块同名的类型的原因。要了解它是如何完成的,请查看FSharpx.Collections代码(以及许多其他项目)。这是一个不太可怕的大文件,这是一个很好的例子:

https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs

namespace FSharpx.Collections

type Queue<'T> (front : list<'T>, rBack : list<'T>) = 
    // ...
    member this.Conj x = 
        match front, x::rBack with
        | [], r -> Queue((List.rev r), [])
        | f, r -> Queue(f, r)
    // ...

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Queue =
    // ...
    let inline conj (x : 'T) (q : Queue<'T>) = (q.Conj x) 
    // ...

当您以这种方式组织代码时,使用同名的类型和模块,F#编译器完全能够保持正确 - 特别是如果您遵循标准命名约定,具有以PascalCase样式命名的成员方法但具有以camelCase样式命名的模块中的函数。 C#编译器会感到困惑,但CompilationRepresentation属性会处理这个问题,确保其他.Net语言看到名为QueueModule的模块。所以来自F#:

let q = new Queue([1;2;3], [])
let moreItems = q |> Queue.conj 4
let stillMoreItems = moreItems.Conj 5

来自C#:

Queue<int> q = new Queue<int>({1,2,3}, {});  // Not valid list syntax, but whatever
Queue<int> moreItems = QueueModule.Conj(4, q);
Queue<int> stillMoreItems = moreItems.Conj(5);

Queue.conj函数在F#中使用看起来更自然,成员方法moreItems.Conj在C#中看起来更自然,但两种语言都可以使用。