使用 AutoOpen 属性的推荐原则是什么?
(这个问题可能是关于何时在类似命名的类型上使用模块函数 VS 静态函数的延续)
专家 F# 指出“这在定义临时顶级运算符和函数时很有用:”
所以这似乎是为了减少模块在代码组织中的作用,当您在技术上需要一个来编写代码但您从客户端的角度删除它的存在时。
还有别的吗?你什么时候使用它?
使用 AutoOpen 属性的推荐原则是什么?
(这个问题可能是关于何时在类似命名的类型上使用模块函数 VS 静态函数的延续)
专家 F# 指出“这在定义临时顶级运算符和函数时很有用:”
所以这似乎是为了减少模块在代码组织中的作用,当您在技术上需要一个来编写代码但您从客户端的角度删除它的存在时。
还有别的吗?你什么时候使用它?
我认为该AutoOpen
属性的主要用途是当您希望在库的用户打开命名空间时使一些 let-bound 值可用。这是该属性非常有用的地方,因为我认为库通常应该导出命名空间中的所有定义,但出于某些目的,您需要导出值,而值不能在命名空间中定义。
这是一个来自F# 异步扩展的示例,它定义了一个计算构建器,因此它需要导出asyncSeq
值(但同时,所有功能都包装在一个命名空间中):
namespace FSharp.Async
type AsyncSeq<'T> = (* ... *)
type AsyncSeqBuilder() = (* ... *)
[<AutoOpen>]
module GlobalValues =
let asyncSeq = AsyncSeqBuilder()
图书馆的用户可以写open FSharp.Async
,他们会看到asyncSeq
。我认为相同的模式用于各种数学库(您还想导出简单命名的函数。)
对于模块(例如List
和Seq
),我认为大多数人不会open
通过模块名称(例如List.map
)来使用和访问功能,因此尽管您可以将其用于嵌套模块,但我没有经常看到这种情况。
它可用于将模块组织成子模块,但在外部呈现统一/单模块视图:
module Outer =
[<AutoOpen>]
module Inner1 =
let f1() = ()
[<AutoOpen>]
module Inner2 =
let f2() = ()
open Outer
let x = f1()
let y = f2()
FParsec这样做:open FParsec
打开所有子模块(Primitives
、CharParsers
等)。
聚会有点晚了,但我想添加另一个用法。
我倾向于使用[<AutoOpen>]
在命名空间中公开类型。
// SlimSql\Types.fs
namespace SlimSql
[<AutoOpen>]
module Types =
type SqlOperation =
{
Statement : string
Parameters : SqlParam list
}
然后我可以将函数附加到相同的类型名称,而不会出现该名称已在使用中的编译器错误。
// SlimSql\SqlOperation.fs
namespace SlimSql
module SqlOperation =
let merge (operations : SqlOperation list) : SqlOperation =
...
let wrapInTransaction operation =
...
然后在消费代码中使用相同的名称很好地打包所有内容。所以当用户在 SqlOperation 数据上寻找一个行为时,他们自然可以通过键入找到它,SqlOperation.
并且 Intellisense 会显示出来。与在实践中使用诸如此类的类型的方式非常相似List
。
open SlimSql
let operations =
[
sql "INSERT INTO ...." [ p "@Value" 123; ... ]
...
]
let writeOp =
operations
|> SqlOperation.merge
|> SqlOperation.wrapInTransaction
SlimSql.Types 模块也可以自己打开,只访问与其他类型组合的类型。
我更喜欢这个解决方案来增加静态成员的类型。