繁体   English   中英

F#记录与班级

[英]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

请注意,链接帖子中的模型是纯粹的,而上面的代码则不是。

我不认为这个问题有一个普遍的答案。 记录和类在某些潜在用途中重叠是肯定的,您可以选择其中任何一种。

值得记住的一个区别是编译器会自动为记录生成结构相等和结构比较,这是您无法免费获得的类。 这就是为什么记录是“数据类型”的明显选择。

在记录和类之间进行选择时,我倾向于遵循的规则是:

  • 使用数据类型的记录(免费获得结构相等)
  • 当我想提供C#友好或.NET风格的公共API时(例如使用可选参数),请使用类。 您也可以使用记录执行此操作,但我发现类更直接
  • 使用本地使用的类型的记录 - 我认为你经常最终直接使用记录(例如创建它们),因此添加/删除字段是更多的工作。 对于仅在单个文件中使用的记录,这不是问题。
  • 如果我需要使用{ ... 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM