kno*_*cte 4 f# constructor types record
假设我想要一个记录类型,例如:
type CounterValues = { Values: (int) list; IsCorrupt: bool }
Run Code Online (Sandbox Code Playgroud)
问题是,我想创建一个构造函数,将整数传递的列表转换为没有负值的新列表(它们将被0替换),并且只有在构造时发现负值时才有IsCorrupt = true .
这可能与F#有关吗?
现在,这就是我所做的,使用属性(但是,它,它不是非常F#-ish,它每次都在getter上调用ConvertAllNegativeValuesToZeroes(),所以效率不高):
type CounterValues
(values: (int) list) =
static member private AnyNegativeValues
(values: (int) list)
: bool =
match values with
| v::t -> (v < 0) || CounterValues.AnyNegativeValues(t)
| [] -> false
static member private ConvertAllNegativeValuesToZeroes
(values: (int) list)
: (int) list =
match values with
| [] -> []
| v::t ->
if (v < 0) then
0::CounterValues.ConvertAllNegativeValuesToZeroes(t)
else
v::CounterValues.ConvertAllNegativeValuesToZeroes(t)
member this.IsCorrupt = CounterValues.AnyNegativeValues(values)
member this.Values
with get()
: (int) list =
CounterValues.ConvertAllNegativeValuesToZeroes(values)
Run Code Online (Sandbox Code Playgroud)
F#中一种相当惯用的方式是使用签名文件来隐藏实现细节,但是一如既往,需要权衡利弊.
想象一下,您已经定义了这样的模型:
module MyDomainModel
type CounterValues = { Values : int list; IsCorrupt : bool }
let createCounterValues values =
{
Values = values |> List.map (max 0)
IsCorrupt = values |> List.exists (fun x -> x < 0)
}
let values cv = cv.Values
let isCorrupt cv = cv.IsCorrupt
Run Code Online (Sandbox Code Playgroud)
请注意,除了检查输入的create函数之外,该模块还包含for Values和的访问器函数IsCorrupt.由于下一步,这是必要的.
到目前为止,MyDomainModel模块中定义的所有类型和功能都是公共的.
不过,现在你添加一个签名文件(一个.fsi文件)之前的.fs包含文件MyDomainModel.在签名文件中,您只将要发布的内容放到外部世界:
module MyDomainModel
type CounterValues
val createCounterValues : values : int list -> CounterValues
val values : counterValues : CounterValues -> int list
val isCorrupt : counterValues : CounterValues -> bool
Run Code Online (Sandbox Code Playgroud)
请注意,声明的模块的名称是相同的,但类型和函数仅在摘要中声明.
因为CounterValues被定义为类型,但没有任何特定结构,因此没有客户端可以创建它的实例.换句话说,这不编译:
module Client
open MyDomainModel
let cv = { Values = [1; 2]; IsCorrupt = true }
Run Code Online (Sandbox Code Playgroud)
编译器抱怨"未定义记录标签'值'".
另一方面,客户端仍然可以访问签名文件定义的功能.这编译:
module Client
let cv = MyDomainModel.createCounterValues [1; 2]
let v = cv |> MyDomainModel.values
let c = cv |> MyDomainModel.isCorrupt
Run Code Online (Sandbox Code Playgroud)
以下是FSI的一些例子:
> createCounterValues [1; -1; 2] |> values;;
val it : int list = [1; 0; 2]
> createCounterValues [1; -1; 2] |> isCorrupt;;
val it : bool = true
> createCounterValues [1; 2] |> isCorrupt;;
val it : bool = false
> createCounterValues [1; 2] |> values;;
val it : int list = [1; 2]
Run Code Online (Sandbox Code Playgroud)
缺点之一是保持签名文件(.fsi)和实现文件(.fs)同步所涉及的开销.
另一个缺点是客户端无法自动访问记录的命名元素.相反,您必须定义和维护像values和的访问器函数isCorrupt.
总而言之,这不是F#中最常用的方法.更常见的方法是提供必要的功能来动态计算这些问题的答案:
module Alternative
let replaceNegatives = List.map (max 0)
let isCorrupt = List.exists (fun x -> x < 0)
Run Code Online (Sandbox Code Playgroud)
如果列表不是太大,那么在运行中计算这样的答案所涉及的性能开销可能小到足以忽略(或者可能通过memoization来解决).
以下是一些用法示例:
> [1; -2; 3] |> replaceNegatives;;
val it : int list = [1; 0; 3]
> [1; -2; 3] |> isCorrupt;;
val it : bool = true
> [1; 2; 3] |> replaceNegatives;;
val it : int list = [1; 2; 3]
> [1; 2; 3] |> isCorrupt;;
val it : bool = false
Run Code Online (Sandbox Code Playgroud)