F# code organization: types & modules

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-29 23:49:28

Here are some notes about the technical distinctions.

Modules can be 'open'ed (unless they have RequireQualifiedAccessAttribute). That is, if you put functions (F and G) in a module (M), then you can write

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

whereas with a static method, you'd always write

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

Module functions cannot be overloaded. Functions in a module are let-bound, and let-bound functions do not permit overloading. If you want to be able to call both

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

you must use members of a type, which only work with 'qualified' calls (e.g. type.StaticMember(...) or object.InstanceMember(...)).

(Are there other differences? I can't recall.)

Those are the main technical differences that influence the choice of one over the other.

Additionally, there is some tendency in the F# runtime (FSharp.Core.dll) to use modules only for F#-specific types (that are typically not used when doing interop with other .Net languages) and static methods for APIs that are more language-neutral. For example, all the functions with curried parameters appear in modules (curried functions are non-trivial to call from other languages).

In F# I prefer a static member on a type over a function in a module if ...

  1. I have to define the type irrespective of the member
  2. The member is functionally related to the type I'm defining

In addition to the other answers there is one more case to use Modules:

For value types they can help to define static properties that do not get re-evaluated every time they are accessed. for example:

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 )

Some big distinctions that weren't originally mentioned:

  • Functions are first class values in F#, but static members are not. So you can write objs |> Seq.map Obj.func but you can't write objs |> Seq.map Obj.Member.

  • Functions can be curried, but members cannot.

  • The compiler will infer types automatically when you call a function, but not when you invoke a member. So you can write let func obj = obj |> Obj.otherFunc but you can't write let func obj = obj.Member.

Since members are more restricted, I usually use functions unless I explicitly want to support OOP/C#.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!