Организация кода 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/С#.