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

Mil*_*oDC 11 f# types naming module

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

我通常会选择以下三种方式之一:

(1):

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

    let myFunction myType y = myType.X + y
Run Code Online (Sandbox Code Playgroud)

(2):

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

[<RequireQualifiedAccess>]
module MyTypeOps =
    let myFunction myType y = myType.X + y
Run Code Online (Sandbox Code Playgroud)

(3):

type MyType =
    {
        x : int
    }

    static member MyFunction myType y = myType.x + y
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

rmu*_*unn 12

还有第四种方法没有列出,即类型和模块名称相同.你认为它无法完成,但它实际上是一种非常普遍的做法; 您只需(在4.1之前的F#版本中)使用[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]模块上的属性.在任何地方编写它都有点难看,这就是为什么F#4.1如果你定义一个与模块同名的类型,它会成为默认行为.要了解它是如何完成的,请查看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) 
    // ...
Run Code Online (Sandbox Code Playgroud)

当您以这种方式组织代码时,使用同名的类型和模块,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
Run Code Online (Sandbox Code Playgroud)

来自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);
Run Code Online (Sandbox Code Playgroud)

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

  • [F# 标准库中的集合类型](https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/list.fs) 也遵循与type' 模式(尽管没有附加到该类型的等效方法)。 (2认同)