如何决定在模块内部编写函数还是作为某种类型的静态成员?
例如,在F#的源代码中,有许多类型与同名模块一起定义,如下所示:
type MyType = // ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MyType = // ...
为什么不简单地将操作定义为MyType类型的静态成员?
答案 0 :(得分:25)
以下是关于技术差异的一些注释。
模块可以'打开'(除非他们有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
,这只适用于“合格”的调用(例如type.StaticMember(...)
或object.InstanceMember(...)
)。
(还有其他差异吗?我不记得了。)
这些是影响一种选择的主要技术差异。
此外,F#运行时(FSharp.Core.dll)中有一些趋势是仅将模块用于F#特定类型(通常在与其他.Net语言进行互操作时不使用)和API的静态方法更加语言中立。例如,所有带有curried参数的函数都出现在模块中(curried函数对于从其他语言调用来说非常重要)。
答案 1 :(得分:3)
在F#中,我更喜欢类型上的静态成员而不是模块中的函数...
答案 2 :(得分:2)
除了其他答案之外,还有一个案例可以使用模块:
对于值类型,它们可以帮助定义每次访问时都不会重新评估的静态属性。例如:
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 )
答案 3 :(得分:0)
最初未提及的一些重大区别:
函数是F#中的第一类值,但静态成员不是。所以你可以写objs |> Seq.map Obj.func
,但不能写objs |> Seq.map Obj.Member
。
功能可以是咖喱,但成员不能。
编译器会在调用函数时自动推断类型,但在调用成员时则不会。所以你可以写let func obj = obj |> Obj.otherFunc
,但不能写let func obj = obj.Member
。
由于成员受到更多限制,我通常会使用函数,除非我明确要支持OOP / C#。