Организация кода F#: типы и модули

Как вы решаете, писать ли функцию внутри модуля или как статический член какого-либо типа?

например, в исходном коде F# существует множество типов, которые определяются вместе с одноименным модулем следующим образом:

type MyType = // ...

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

почему бы вам просто не определить операции как статические члены типа MyType?

4 ответов


вот некоторые заметки о технических различий.

модули могут быть открытой объед (если они не имеют 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)

вы должны использовать memberS типа, который работает только с "квалифицированными" вызовами (например,type.StaticMember(...) или object.InstanceMember(...)).

(есть ли другие различия? Не могу вспомнить.)

таковы основные технические различия, которые влияют на выбор одного над другим.

кроме того, существует некоторая тенденция в среде выполнения F# (FSharp.Ядро.dll) использовать модули только для F#-specific типы (которые обычно не используются при взаимодействии с другими языками .Net) и статические методы для API, которые более нейтральны к языку. Например, все функции с параметрами Карри отображаются в модулях (функции Карри нетривиальны для вызова с других языков).


В F# я предпочитаю статический член типа функции в модуле if ...

  1. Я должен определить тип независимо от члена
  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 )

некоторые большие различия, которые изначально не упоминались:

  • функции являются значениями первого класса В F#, но статические члены-нет. Так что вы можете написать objs |> Seq.map Obj.func но ты не можешь писать objs |> Seq.map Obj.Member.

  • функции могут быть curried, но члены не могут.

  • компилятор автоматически выводит типы при вызове функции, но не при вызове члена. Так что вы можете написать let func obj = obj |> Obj.otherFunc но вы не можете написать let func obj = obj.Member.

поскольку члены более ограничены, я обычно использую функции, если я явно не хочу поддерживать OOP/C#.