23

您如何决定在模块内编写函数还是作为某种类型的静态成员?

例如,在 F# 的源代码中,有很多类型与同名模块一起定义,如下所示:

type MyType = // ...

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MyType = // ...

为什么不简单地将操作定义为 MyType 类型的静态成员?

4

4 回答 4

30

以下是有关技术区别的一些说明。

模块可以“打开”(除非它们具有 RequireQualifiedAccessAttribute)。也就是说,如果你把函数(FG)放在一个模块(M)中,那么你可以写

open M
... F x ... G x ...

而使用静态方法,你总是会写

... M.F x ... M.G x ...

模块函数不能重载。模块中的函数是 let-bound,而 let-bound 函数不允许重载。如果您希望能够同时调用两者

X.F(someInt)
X.F(someInt, someString)

您必须使用members 类型,它只适用于“合格”调用(例如type.StaticMember(...)or object.InstanceMember(...))。

(还有其他区别吗?我不记得了。)

这些是影响选择另一种的主要技术差异。

此外,F# 运行时 (FSharp.Core.dll) 中存在一些趋势,即仅将模块用于 F# 特定类型(在与其他 .Net 语言进行互操作时通常不使用)和更多语言的 API 的静态方法-中性的。例如,所有带有 curried 参数的函数都出现在模块中(curried 函数对于从其他语言调用非常重要)。

于 2010-02-06T19:10:08.667 回答
4

在 F# 中,如果 ...

  1. 无论成员如何,我都必须定义类型
  2. 该成员在功能上与我定义的类型相关
于 2010-02-06T18:40:34.643 回答
4

除了其他答案之外,还有另一种使用模块的情况:

对于值类型,它们可以帮助定义不会在每次访问时重新评估的静态属性。例如:

type [<Struct>] Point =
    val x:float
    val y:float
    new (x,y) = {x=x;y=y}

    static member specialPoint1 = // sqrt is computed every time the property is accessed
        Point (sqrt 0.5 , sqrt 0.5 )

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Point = 

    let specialPoint2 = // sqrt is computed only once when the Module is opened
        Point (sqrt 0.5 , sqrt 0.5 )
于 2014-05-30T15:02:00.753 回答
1

最初没有提到的一些重大区别:

  • 函数是 F# 中的一等值,但静态成员不是。所以你可以写objs |> Seq.map Obj.func,但你不能写objs |> Seq.map Obj.Member

  • 函数可以被柯里化,但成员不能。

  • 编译器将在您调用函数时自动推断类型,但在您调用成员时不会。所以你可以写let func obj = obj |> Obj.otherFunc,但你不能写let func obj = obj.Member

由于成员受到更多限制,我通常使用函数,除非我明确想要支持 OOP/C#。

于 2017-07-14T21:26:42.883 回答