Организация кода 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.Core.dll) существует некоторая тенденция использовать модули только для F#-специфических типов (которые обычно не используются при взаимодействии с другими языками.Net) и статические методы для API, которые являются более языковыми. -нейтральное. Например, все функции с параметрами карри появляются в модулях (функции карри не тривиальны для вызова из других языков).

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

  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,

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

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

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

Другие вопросы по тегам