我曾经认为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
和帮助函数做。哪个plays better with functional concepts,如模式匹配,类型推断,高阶函数,泛型相等......
进一步分析,Record
可以被视为makeRecord()
构造函数实现的接口。应用(排序)关注点分离,makeRecord
函数中的逻辑可以更改,而不会有违反契约的风险,即记录字段。
当使用与类型名称匹配的模块替换makeRecord
函数时,这种分离变得明显(参考圣诞节Tree Record)。
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
?
请注意,链接帖子中的模型是纯粹的,而上面的代码则不是。
答案 0 :(得分:10)
我认为这个问题没有一个普遍的答案。记录和类在某些潜在用途中重叠是肯定的,您可以选择其中任何一种。
值得记住的一个区别是编译器会自动为记录生成结构相等和结构比较,这是您无法免费获得的类。这就是为什么记录是“数据类型”的明显选择。
在记录和选择之间我倾向于遵循的规则课程是:
{ ... with ... }
语法创建克隆,请使用记录。如果你正在编写一些递归处理并且需要保持状态,那就特别好了。我不认为每个人都会同意这一点并且它并不涵盖所有选择 - 但一般而言,使用数据记录和本地类型以及其他类似乎是在两者之间进行选择的合理方法。
答案 1 :(得分:6)
如果你想在记录中实现隐藏数据,我觉得有更好的方法可以实现,例如abstract data type&#34; pattern&#34;。
看看这个:
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所提到的,记录的卖点。 至于作为接口的记录,您可能会看到sometimes in the field,但我认为它更像是一个JavaScript / Haskell成语。与那些语言不同,F#具有.NET的接口系统,与object expressions结合使用时更加强大。我觉得没有太多理由为此重新调整记录。