[英]F# Record vs Class
我曾经把Record
视为(不可变)数据的容器,直到我遇到一些有启发性的阅读。
鉴于函数可以被视为F#中的值,记录字段也可以保存函数值。 这提供了状态封装的可能性。
module RecordFun =
type CounterRecord = {GetState : unit -> int ; Increment : unit -> unit}
// Constructor
let makeRecord() =
let count = ref 0
{GetState = (fun () -> !count) ; Increment = (fun () -> incr count)}
module ClassFun =
// Equivalent
type CounterClass() =
let count = ref 0
member x.GetState() = !count
member x.Increment() = incr count
用法
counter.GetState()
counter.Increment()
counter.GetState()
看来,除了继承之外,你无法用一个Class
来做,你无法用一个Record
和一个帮助函数。 其功能概念更好 ,例如模式匹配,类型推断,高阶函数,通用等式......
进一步分析, Record
可以看作是makeRecord()
构造函数实现的接口 。 应用(排序)关注点分离,其中makeRecord
函数中的逻辑可以更改而没有违反合同的风险,即记录字段。
将makeRecord
函数替换为与类型名称匹配的模块(ref圣诞树记录 )时,这种分离变得明显。
module RecordFun =
type CounterRecord = {GetState : unit -> int ; Increment : unit -> unit}
// Module showing allowed operations
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module CounterRecord =
let private count = ref 0
let create () =
{GetState = (fun () -> !count) ; Increment = (fun () -> incr count)}
问:是否应将记录视为数据的简单容器,或者状态封装是否有意义? 我们应该在哪里绘制线,何时应该使用Class
而不是Record
?
请注意,链接帖子中的模型是纯粹的,而上面的代码则不是。
我不认为这个问题有一个普遍的答案。 记录和类在某些潜在用途中重叠是肯定的,您可以选择其中任何一种。
值得记住的一个区别是编译器会自动为记录生成结构相等和结构比较,这是您无法免费获得的类。 这就是为什么记录是“数据类型”的明显选择。
在记录和类之间进行选择时,我倾向于遵循的规则是:
{ ... with ... }
语法创建克隆,请使用记录。 如果您正在编写一些递归处理并需要保持状态,这一点特别好。 我不认为每个人都会同意这一点,并不是涵盖所有选择 - 但一般来说,使用数据记录和本地类型以及其他类似乎是在两者之间进行选择的合理方法。
如果你想在记录中实现数据隐藏,我觉得有更好的方法来实现它,比如抽象数据类型 “模式”。
看看这个:
type CounterRecord =
private {
mutable count : int
}
member this.Count = this.count
member this.Increment() = this.count <- this.count + 1
static member Make() = { count = 0 }
Make
成员, count
领域是可变的 - 不是值得骄傲的事情,但我会说你公平的比赛例子。 此外,由于私有修饰符,它无法从模块外部访问。 要从外部访问它,您具有只读Count
属性。 Increment
函数可以改变内部状态。 CounterRecord
实例 - 正如Tomas所提到的,记录的卖点。 至于记录作为接口,你可能会看到有时 在现场 ,虽然我认为它更像是一个JavaScript / Haskell成语。 与那些语言不同,F#具有.NET的接口系统,与对象表达式结合使用时更加强大。 我觉得没有太多理由为此重新调整记录。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.