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

2024-03-18

假设我想要一个记录类型,例如:

type CounterValues = { Values: (int) list; IsCorrupt: bool }

问题是,我想创建一个构造函数,将传递的整数列表转换为一个没有负值的新列表(它们将被 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)

A F# 中相当惯用的方法是使用签名文件隐藏实施细节 http://fsharpforfunandprofit.com/posts/designing-with-types-single-case-dus,但一如既往,这涉及到权衡。

想象一下您已经这样定义了模型:

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

请注意,除了create检查输入的函数,该模块还包含以下访问器函数Values and IsCorrupt。这是必要的,因为下一步。

到目前为止,定义在MyDomainModel模块是公共的。

但是,现在您添加一个签名文件 (a .fsi file) before the .fs文件包含MyDomainModel。在签名文件中,您只放置想要发布到外界的内容:

module MyDomainModel

type CounterValues
val createCounterValues : values : int list -> CounterValues
val values : counterValues : CounterValues -> int list
val isCorrupt : counterValues : CounterValues -> bool

请注意,声明的模块名称是相同的,但类型和函数仅在抽象中声明。

Because CounterValues被定义为一种类型,但没有任何特定的结构,任何客户端都无法创建它的实例。换句话说,这不能编译:

module Client

open MyDomainModel

let cv = { Values = [1; 2]; IsCorrupt = true }

编译器抱怨“记录标签‘Values’未定义”。

另一方面,客户端仍然可以访问签名文件定义的功能。这编译:

module Client

let cv = MyDomainModel.createCounterValues [1; 2]
let v = cv |> MyDomainModel.values
let c = cv |> MyDomainModel.isCorrupt

以下是 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]

缺点之一是保留签名文件会产生开销(.fsi)和实现文件(.fs) 同步中。

另一个缺点是客户端无法自动访问记录的命名元素。相反,您必须定义和维护访问器函数,例如values and isCorrupt.


尽管如此,这并不是 F# 中最常见的方法。更常见的方法是提供必要的函数来动态计算此类问题的答案:

module Alternative

let replaceNegatives = List.map (max 0)

let isCorrupt = List.exists (fun x -> x < 0)

如果列表不太大,则动态计算此类答案所涉及的性能开销可能小到足以忽略(或者可以通过记忆来解决)。

以下是一些使用示例:

> [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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

公共记录类型的构造函数? 的相关文章

随机推荐