F#:组织类型和模块的最佳实践
F#: Best practices for organizing types and modules
如果我采用定义类型的函数式方法,然后描述在该类型上运行的函数(与实例方法相对),我应该如何组织我的代码?
我通常选择以下三种方式之一:
(1):
module MyType =
type MyType (x : int) =
member val X = x with get
let myFunction myType y = myType.X + y
(2):
type MyType (x : int) =
member val X = x with get
[<RequireQualifiedAccess>]
module MyTypeOps =
let myFunction myType y = myType.X + y
(3):
type MyType =
{
x : int
}
static member MyFunction myType y = myType.x + y
(1)的优点是函数定义在一个模块中。 (1) 的缺点是类型也在模块中定义,导致实例化时丑陋的 MyType.MyType
冗余,并且如果你想允许 open MyType
作为一项工作,则无法 [<RequireQualifiedAccess>]
-围绕该冗余。
(2) 的优点是在模块中定义的功能,没有 module.type 冗余。 Con是模块不能与类型重名。
(3) 的优点是方法是静态的,没有 module.type 冗余。缺点是您不能在此方法下定义某些类型(例如非方法值和活动模式)。
通常情况下,我更喜欢 (2),尽管我讨厌必须为模块命名一些描述性和直观性不如应有的名称。 (2) 还允许我在极少数情况下使用 type ... and ...
创建相互依赖的类型,这对于方法 (1).
等单独模块中定义的类型显然是不可能的
我的 F# 程序员采用什么方法?我想知道我是否忽略了一些明显的(或不那么明显的)东西,或者,如果我没有,是否有一个约定可以解决无法将模块命名为与其内部相应类型相同的名称的问题相同的命名空间。
还有第四种你没有列出的方法,就是让类型和模块命名相同。你觉得做不到,其实是很普遍的做法;您只需(在 4.1 之前的 F# 版本中)在模块上使用 [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
属性。到处都这样写有点难看,这就是为什么 F# 4.1 made this the default behavior 如果你定义一个与模块同名的类型。要了解它是如何完成的,请查看 FSharpx.Collections 代码(以及许多其他项目)。这是一个不太大的文件,这是一个很好的例子:
https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs
namespace FSharpx.Collections
type Queue<'T> (front : list<'T>, rBack : list<'T>) =
// ...
member this.Conj x =
match front, x::rBack with
| [], r -> Queue((List.rev r), [])
| f, r -> Queue(f, r)
// ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Queue =
// ...
let inline conj (x : 'T) (q : Queue<'T>) = (q.Conj x)
// ...
当您以这种方式组织您的代码时,使用同名的类型和模块,F# 编译器完全能够保持直截了当——特别是如果您遵循标准命名约定,成员方法以 PascalCase 风格命名但在以驼峰命名风格命名的模块中具有功能。 C# 编译器会感到困惑,但是 CompilationRepresentation
属性会解决这个问题,确保其他 .Net 语言可以看到名称为 QueueModule
的模块。所以从 F#:
let q = new Queue([1;2;3], [])
let moreItems = q |> Queue.conj 4
let stillMoreItems = moreItems.Conj 5
来自 C#:
Queue<int> q = new Queue<int>({1,2,3}, {}); // Not valid list syntax, but whatever
Queue<int> moreItems = QueueModule.Conj(4, q);
Queue<int> stillMoreItems = moreItems.Conj(5);
Queue.conj
函数在 F# 中看起来更自然,成员方法 moreItems.Conj
在 C# 中看起来更自然,但两种语言都可用。
如果我采用定义类型的函数式方法,然后描述在该类型上运行的函数(与实例方法相对),我应该如何组织我的代码?
我通常选择以下三种方式之一:
(1):
module MyType =
type MyType (x : int) =
member val X = x with get
let myFunction myType y = myType.X + y
(2):
type MyType (x : int) =
member val X = x with get
[<RequireQualifiedAccess>]
module MyTypeOps =
let myFunction myType y = myType.X + y
(3):
type MyType =
{
x : int
}
static member MyFunction myType y = myType.x + y
(1)的优点是函数定义在一个模块中。 (1) 的缺点是类型也在模块中定义,导致实例化时丑陋的 MyType.MyType
冗余,并且如果你想允许 open MyType
作为一项工作,则无法 [<RequireQualifiedAccess>]
-围绕该冗余。
(2) 的优点是在模块中定义的功能,没有 module.type 冗余。 Con是模块不能与类型重名。
(3) 的优点是方法是静态的,没有 module.type 冗余。缺点是您不能在此方法下定义某些类型(例如非方法值和活动模式)。
通常情况下,我更喜欢 (2),尽管我讨厌必须为模块命名一些描述性和直观性不如应有的名称。 (2) 还允许我在极少数情况下使用 type ... and ...
创建相互依赖的类型,这对于方法 (1).
我的 F# 程序员采用什么方法?我想知道我是否忽略了一些明显的(或不那么明显的)东西,或者,如果我没有,是否有一个约定可以解决无法将模块命名为与其内部相应类型相同的名称的问题相同的命名空间。
还有第四种你没有列出的方法,就是让类型和模块命名相同。你觉得做不到,其实是很普遍的做法;您只需(在 4.1 之前的 F# 版本中)在模块上使用 [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
属性。到处都这样写有点难看,这就是为什么 F# 4.1 made this the default behavior 如果你定义一个与模块同名的类型。要了解它是如何完成的,请查看 FSharpx.Collections 代码(以及许多其他项目)。这是一个不太大的文件,这是一个很好的例子:
https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/Queue.fs
namespace FSharpx.Collections
type Queue<'T> (front : list<'T>, rBack : list<'T>) =
// ...
member this.Conj x =
match front, x::rBack with
| [], r -> Queue((List.rev r), [])
| f, r -> Queue(f, r)
// ...
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Queue =
// ...
let inline conj (x : 'T) (q : Queue<'T>) = (q.Conj x)
// ...
当您以这种方式组织您的代码时,使用同名的类型和模块,F# 编译器完全能够保持直截了当——特别是如果您遵循标准命名约定,成员方法以 PascalCase 风格命名但在以驼峰命名风格命名的模块中具有功能。 C# 编译器会感到困惑,但是 CompilationRepresentation
属性会解决这个问题,确保其他 .Net 语言可以看到名称为 QueueModule
的模块。所以从 F#:
let q = new Queue([1;2;3], [])
let moreItems = q |> Queue.conj 4
let stillMoreItems = moreItems.Conj 5
来自 C#:
Queue<int> q = new Queue<int>({1,2,3}, {}); // Not valid list syntax, but whatever
Queue<int> moreItems = QueueModule.Conj(4, q);
Queue<int> stillMoreItems = moreItems.Conj(5);
Queue.conj
函数在 F# 中看起来更自然,成员方法 moreItems.Conj
在 C# 中看起来更自然,但两种语言都可用。