Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oop F#记录vs类_Oop_Types_F# - Fatal编程技术网

Oop F#记录vs类

Oop F#记录vs类,oop,types,f#,Oop,Types,F#,我曾经认为记录是(不可变)数据的容器,直到我遇到一些启发性的阅读 鉴于函数可以被视为F#中的值,记录字段也可以保存函数值。这为状态封装提供了可能性 module RecordFun = type CounterRecord = {GetState : unit -> int ; Increment : unit -> unit} // Constructor let makeRecord() = let count = ref 0

我曾经认为
记录
是(不可变)数据的容器,直到我遇到一些启发性的阅读

鉴于函数可以被视为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
用法

看起来,除了继承之外,对于
,除了
记录
和助手函数之外,没有什么可以做的。如模式匹配、类型推理、高阶函数、泛型等式等

进一步分析,
Record
可以看作是由
makeRecord()
构造函数实现的接口。应用(某种)关注点分离,其中
makeRecord
功能中的逻辑可以更改,而不会有违反合同的风险,即记录字段

当将
makeRecord
函数替换为与类型名称匹配的模块时,这种分离就变得明显了(请参见)

modulerecordfun=
类型计数器记录={GetState:unit->int;Increment:unit->unit}
//显示允许的操作的模块
[]
模块计数器记录=
让私有计数=ref 0
让我们创建()=
{GetState=(fun()->!count);Increment=(fun()->incr count)}
问:记录应该被视为数据的简单容器,还是状态封装有意义?我们应该在哪里划清界限,什么时候应该使用
类而不是
记录


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

我认为这个问题没有一个统一的答案。的确,记录和类在某些潜在用途上是重叠的,您可以选择其中之一

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

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

  • 对数据类型使用记录(免费获得结构相等)
  • 当我想提供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 }
类型计数器记录=
私人{
可变计数:int
}
成员this.Count=this.Count

member this.Increment()=this.count有趣的问题;一般来说,在函数语言中限制可变值的使用是一个好主意。(向下滚动阅读关于尊敬的Qc Na大师的故事)另请参见:我投票结束这个问题,因为它与网站上的其他问题部分重复(见其他链接),并且至少部分基于观点,或者“过于宽泛”。与您的问题有关。我同意,这是一个宽泛的话题,需要一些有经验的观点。感谢您的见解。这个答案缺少一点,那就是性能。如果您查看编译后的代码,类显示得更快,因为它们删除了
F的间接指向SharpFunc
,记录解决方案将生成它。这种封装方式肯定更简单(也更熟悉)。有趣的帖子,谢谢。它有点违背了最初使用记录的目的(模式匹配、类型推断等)当然,在
反记录的背景下,你是绝对正确的。你链接到的文章过分强调了这些缺点,而实际上这些缺点是很小的不便。有一个很好的方法可以指出这些缺点。
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)}
type CounterRecord = 
    private { 
        mutable count : int 
    }
    member this.Count = this.count
    member this.Increment() = this.count <- this.count + 1
    static member Make() = { count = 0 }