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

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

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

type MyType = // ...

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

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

Ответ 1

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

Модули могут быть "открытыми" (если только они не имеют RequireQualifiedAccessAttribute). То есть, если вы поместите функции (F и G) в модуль (M), вы можете написать

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

тогда как со статическим методом вы всегда будете писать

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

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

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

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

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

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

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

Ответ 2

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

  • Мне нужно определить тип независимо от члена
  • Элемент функционально связан с типом, который я определяю

Ответ 3

В дополнение к другим ответам есть еще один пример использования модулей:

Для типов значений они могут помочь определить статические свойства, которые не подвергаются переоценке каждый раз, когда к ним обращаются. например:

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 )

Ответ 4

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

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

  • Функции могут быть нарисованы, но члены не могут.

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

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