public 记录类型的构造函数?
Constructor on public record type?
假设我想要一个记录类型,例如:
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)
前一段时间我看了一些关于抽象数据类型 (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 允许模块与类型同名。
private 可访问性将阻止从其他模块直接访问记录字段。您可以向 CounterValues 模块添加函数,以对来自传入的 CounterValues 类型的 and/or return 数据进行操作。请注意我是如何添加 isCorrupt 和 count 这两个函数来处理 CounterValues 类型的。
你不能有构造函数,但我通常看到使用的是静态工厂方法:
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
不同步
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 函数外,该模块还包含 Values
和 IsCorrupt
的访问函数。这是必要的,因为下一步。
到目前为止,MyDomainModel
模块中定义的所有类型和函数都是 public.
但是,现在您添加一个 签名文件(一个 .fsi
文件)在 .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 }
编译器抱怨 "The record label 'Values' is not defined"。
另一方面,客户端仍然可以访问签名文件定义的功能。 这样编译:
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
和 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
这是凯文回答的变体,但将
CounterValues
键入一个模块,并使用一次传递 ('List.foldBack') 来
进行验证。
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)
但我同意 Mauricio,布尔值不好。你考虑过这样的受歧视的联合类型吗?
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。
最后,我选择了我最初提出的低效解决方案、来自@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,这是最简单的解决方案。
假设我想要一个记录类型,例如:
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)
前一段时间我看了一些关于抽象数据类型 (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 允许模块与类型同名。 private 可访问性将阻止从其他模块直接访问记录字段。您可以向 CounterValues 模块添加函数,以对来自传入的 CounterValues 类型的 and/or return 数据进行操作。请注意我是如何添加 isCorrupt 和 count 这两个函数来处理 CounterValues 类型的。
你不能有构造函数,但我通常看到使用的是静态工厂方法:
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
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 函数外,该模块还包含 Values
和 IsCorrupt
的访问函数。这是必要的,因为下一步。
到目前为止,MyDomainModel
模块中定义的所有类型和函数都是 public.
但是,现在您添加一个 签名文件(一个 .fsi
文件)在 .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 }
编译器抱怨 "The record label 'Values' is not defined"。
另一方面,客户端仍然可以访问签名文件定义的功能。 这样编译:
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
和 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
这是凯文回答的变体,但将
CounterValues
键入一个模块,并使用一次传递 ('List.foldBack') 来
进行验证。
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)
但我同意 Mauricio,布尔值不好。你考虑过这样的受歧视的联合类型吗?
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。
最后,我选择了我最初提出的低效解决方案、来自@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,这是最简单的解决方案。