您如何决定在模块内编写函数还是作为某种类型的静态成员?
例如,在 F# 的源代码中,有很多类型与同名模块一起定义,如下所示:
type MyType = // ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MyType = // ...
为什么不简单地将操作定义为 MyType 类型的静态成员?
您如何决定在模块内编写函数还是作为某种类型的静态成员?
例如,在 F# 的源代码中,有很多类型与同名模块一起定义,如下所示:
type MyType = // ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MyType = // ...
为什么不简单地将操作定义为 MyType 类型的静态成员?
以下是有关技术区别的一些说明。
模块可以“打开”(除非它们具有 RequireQualifiedAccessAttribute)。也就是说,如果你把函数(F
和G
)放在一个模块(M
)中,那么你可以写
open M
... F x ... G x ...
而使用静态方法,你总是会写
... M.F x ... M.G x ...
模块函数不能重载。模块中的函数是 let-bound,而 let-bound 函数不允许重载。如果您希望能够同时调用两者
X.F(someInt)
X.F(someInt, someString)
您必须使用member
s 类型,它只适用于“合格”调用(例如type.StaticMember(...)
or object.InstanceMember(...)
)。
(还有其他区别吗?我不记得了。)
这些是影响选择另一种的主要技术差异。
此外,F# 运行时 (FSharp.Core.dll) 中存在一些趋势,即仅将模块用于 F# 特定类型(在与其他 .Net 语言进行互操作时通常不使用)和更多语言的 API 的静态方法-中性的。例如,所有带有 curried 参数的函数都出现在模块中(curried 函数对于从其他语言调用非常重要)。
在 F# 中,如果 ...
除了其他答案之外,还有另一种使用模块的情况:
对于值类型,它们可以帮助定义不会在每次访问时重新评估的静态属性。例如:
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 )
最初没有提到的一些重大区别:
函数是 F# 中的一等值,但静态成员不是。所以你可以写objs |> Seq.map Obj.func
,但你不能写objs |> Seq.map Obj.Member
。
函数可以被柯里化,但成员不能。
编译器将在您调用函数时自动推断类型,但在您调用成员时不会。所以你可以写let func obj = obj |> Obj.otherFunc
,但你不能写let func obj = obj.Member
。
由于成员受到更多限制,我通常使用函数,除非我明确想要支持 OOP/C#。