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

时间:2015-08-11 13:52:03

标签: types constructor f# record

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

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

问题是,我想创建一个构造函数,将整数传递的列表转换为没有负值的新列表(它们将被0替换),并且只有在找到负值时才有IsCorrupt = true在施工时。

这可以用F#吗?

现在,这就是我所做的,使用属性(但是,嗯,它不是非常F#-ish,并且它每次都在getter上调用ConvertAllNegativeValuesToZeroes(),所以它是#s>效率不高):

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)

5 个答案:

答案 0 :(得分:5)

A fairly idiomatic way in F# is to use signature files to hide implementation details,但与往常一样,需要权衡利弊。

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

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 函数外,此模块还包含ValuesIsCorrupt的访问函数。由于下一步,这是必要的。

到目前为止,MyDomainModel模块中定义的所有类型和函数都是公开的。

但是,现在您在包含.fsi的{​​{1}}文件之前添加签名文件.fs文件)。在签名文件中,您只将要发布的内容放到外部世界:

MyDomainModel

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

由于module MyDomainModel type CounterValues val createCounterValues : values : int list -> CounterValues val values : counterValues : CounterValues -> int list val isCorrupt : counterValues : CounterValues -> bool 被定义为类型,但没有任何特定结构,因此没有客户端可以创建它的实例。换句话说,这不会编译:

CounterValues

编译器抱怨“未定义记录标签'值'。”

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

module Client

open MyDomainModel

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

以下是FSI的一些例子:

module Client

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

缺点之一是保持签名文件(> 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)同步所涉及的开销。

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

所有这一切,这不是F#中最常用的方法。更常见的方法是提供必要的功能来动态计算这些问题的答案:

isCorrupt

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

以下是一些使用示例:

module Alternative

let replaceNegatives = List.map (max 0)

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

答案 1 :(得分:4)

我在前一段时间看过抽象数据类型(ADT),并使用了这个结构。它对我有用。

type CounterValues = private { Values: int list; IsCorrupt: bool }
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module CounterValues =

  let create values =
    let validated = 
      values 
      |> List.map (fun v -> if v < 0 then 0 else v)
    {Values = validated; IsCorrupt = validated <> values}

  let isCorrupt v =
    v.IsCorrupt

  let count v =
    List.length v.Values

CompilationRepresentation 允许模块与类型具有相同的名称。 私有辅助功能将阻止直接访问其他模块的记录字段。您可以向CounterValues模块添加函数,以便对传入的CounterValues类型进行操作和/或返回数据。请注意我如何添加两个函数 isCorrupt count 来使用CounterValues类型。

答案 2 :(得分:2)

这是凯文答案的一个变种,但是把它放了 CounterValues在模块中输入,并使用一个传递(&#39; List.foldBack&#39;)来 做验证。

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

    /// Create a CounterValues from a list of ints
    let Create intList =

        // helper for foldBack below
        let folder i (values,isCorrupt) =
            if i < 0 then 
                (0::values,true)
            else
                (i::values,isCorrupt)
        // one pass through the list to detect and fix bad values
        let newValues,isCorrupt = List.foldBack folder intList ([],false)

        // create a CounterValues 
        {Values=newValues; IsCorrupt=isCorrupt}

    /// Get the contents of a CounterValues 
    let Get cv =
        cv.Values, cv.IsCorrupt

代码的使用方式如下:

// direct access fails
// let cv = API.CounterValues  // error

// using "factory" function
let cv1 = API.Create [1;2;3;4] 
cv1 |> API.Get    // ([1; 2; 3; 4], false)

let cv2 = API.Create [1;2;-3;4] 
cv2 |> API.Get    // ([1; 2; 0; 4], true)

但是我和毛里西奥说布尔人很糟糕。您是否考虑过像这样的歧视工会类型?

module API =
    type CounterValues = 
        private 
        | NonCorrupt of int list 
        | Corrupt of int list 

    /// Create a CounterValues from a list of ints
    let Create intList =

        // helper for foldBack below
        let folder i (values,isCorrupt) =
            if i < 0 then 
                (0::values,true)
            else
                (i::values,isCorrupt)
        // one pass through the list to detect and fix bad values
        let newValues,isCorrupt = List.foldBack folder intList ([],false)

        // create a CounterValues 
        if isCorrupt then Corrupt newValues else NonCorrupt newValues

    /// Get the contents of a CounterValues using active patterns
    let (|NonCorrupt|Corrupt|) cv =
        match cv with 
        | Corrupt intList -> Corrupt intList 
        | NonCorrupt intList -> NonCorrupt intList 

然后你可以在使用它时模式匹配:

// helper to pattern match
let print cv = 
    match cv with
    | API.Corrupt intList -> 
        printfn "Corrupt %A" intList  
    | API.NonCorrupt intList -> 
        printfn "NonCorrupt %A" intList  

let cv1 = API.Create [1;2;3;4] 
cv1 |> print   // NonCorrupt [1; 2; 3; 4]

let cv2 = API.Create [1;2;-3;4] 
cv2 |> print   // Corrupt [1; 2; 0; 4]

我还有一些关于约束类型here的例子。

答案 3 :(得分:1)

最后,我选择了我最初的无效提议解决方案,来自@Grundoon的foldBack算法,以及仅代表在施工时创建的值的高效属性(因此它们不再低效,它们不会被评估)每一次):

type CounterValues
    (values: (int) list) =

    // helpers for foldBack below
    let folder v (values,isCorrupt) =
        if v < 0 then 
            (0::values,true)
        else
            (v::values,isCorrupt)

    // one pass through the list to detect and fix bad values
    let curatedValues,isCorrupt = 
        List.foldBack folder vals ([],false)

    member this.IsCorrupt
        with get()
            : bool =
                isCorrupt

    member this.Values
        with get()
            : (int) list =
                curatedValues

哪个是最简单的解决方案,IMO。

答案 4 :(得分:0)

你不能拥有构造函数,但我通常看到的是一个静态工厂方法:

type CounterValues = 
    { Values: int list; IsCorrupt: bool }
    static member Make(values: int list) = 
         // do your work here, returning the constructed record.

此外,这是一个记录,而不是一个有区别的联盟。

修改:我在评论中描述的内容是这样的:

type CounterValues = 
    { Values: int list }
    member this.IsCorrupt = 
        this.Values
        |> List.tryFind (fun x -> x < 0)
        |> Option.isSome

这样,您的记录有一个字段 - Values,您在使用标准语法构造记录时提供该字段。 IsCorrupt被编译为只读属性,当您访问它时将重新计算,这意味着它不会与Values

不同步