公共记录类型的构造函数?

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)

Mar*_*ann 5

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)