Организация кода 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)
ты должен использовать member
s типа, которые работают только с "квалифицированными" вызовами (например, type.StaticMember(...)
или же object.InstanceMember(...)
).
(Есть ли другие различия? Я не могу вспомнить.)
Это основные технические различия, которые влияют на выбор одного над другим.
Кроме того, во время выполнения F# (FSharp.Core.dll) существует некоторая тенденция использовать модули только для F#-специфических типов (которые обычно не используются при взаимодействии с другими языками.Net) и статические методы для API, которые являются более языковыми. -нейтральное. Например, все функции с параметрами карри появляются в модулях (функции карри не тривиальны для вызова из других языков).
В F# я предпочитаю статический член типа над функцией в модуле, если...
- Я должен определить тип независимо от члена
- Элемент функционально связан с типом, который я определяю
В дополнение к другим ответам есть еще один случай использования модулей:
Для типов значений они могут помочь определить статические свойства, которые не переоцениваются при каждом обращении к ним. например:
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#.