克隆一个类实例,只改变其中几个属性

12

我想知道在F#中是否有一种方法可以简单地克隆一个类实例,并仅更改其中一个或几个属性。

我知道对于记录类型,这是可能的:

let p2 = {p1 with Y = 0.0}

我决定采用面向对象编程,以保持我的代码整洁和函数有序。 - NoIdeaHowToFixThis
1
@GuyCoder:“对于类型内在操作,请使用属性和方法。这是特别指出的,因为一些来自函数式编程背景的人避免同时使用面向对象编程,而更喜欢包含一组函数定义与类型相关的内在函数的模块(例如,长度foo而不是foo.Length)。总的来说,在F#中,面向对象编程的使用被视为首选的软件工程设备...”(http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/fsharp-component-design-guidelines.pdf) - NoIdeaHowToFixThis
2个回答

12

模拟类的复制和更新表达式的一种方法是使用带有可选参数的复制构造函数。

type Person(first, last, age) =
  new (prototype: Person, ?first, ?last, ?age) =
    Person(defaultArg first prototype.First, 
           defaultArg last prototype.Last, 
           defaultArg age prototype.Age)
  member val First = first
  member val Last = last
  member val Age = age

let john = Person("John", "Doe", 45)
let jane = Person(john, first="Jane")

编辑

虽然你并没有要求这个,但在很多情况下,将类设为可变的会导致更清晰的代码:

type Person(first, last, age) =
  member val First = first with get, set
  member val Last = last with get, set
  member val Age = age with get, set
  member this.Clone() = this.MemberwiseClone() :?> Person

let john = Person("John", "Doe", 45)
let jane = john.Clone() in jane.First <- "Jane"

2
好主意。然而,我很惊讶没有一个习语表达来实现这一点。 - NoIdeaHowToFixThis
1
我认为可以公正地说,F#的面向对象方面基本上是从主流的.NET语言中复制过来的。虽然有一些补充,主要是为了支持更函数式的风格,比如对象表达式和默认的不可变性。但总体而言,在.NET上的面向对象编程在F#中基本上保持不变。 - Daniel
1
据我所知,Scala引入了复制和更新表达式来鼓励使用具有不可变属性的类。复制和更新鼓励创建新实例,即使只更新了一个属性,也无需在从头创建构造函数中声明所有内容。沿着这条思路,我期望F#有惯用的复制和更新表达式。无论如何,您的解决方案很棒。我接受了它并再次感谢您的帮助。 - NoIdeaHowToFixThis
1
不客气。在我看来,在 F# 中为类进行复制和更新并没有提供明显的好处。已经有了支持此功能的记录类型。如果您打算频繁更改属性,则可以定义一个“Clone”方法并使它们可变。我在我的答案中添加了一个示例,以便您可以比较这两种方法。 - Daniel
@Daniel,我真的认为类的复制和更新会带来巨大的好处。它将使与例如WSDL和其他服务的交互变得非常容易,并使F#成为数据交互的“首选语言”。我甚至认为我们应该更进一步,提供模式匹配复制功能,使用运算符映射到特定的属性名称。在转换非结构化或模棱两可的数据类型时具有类型安全性。 - Mr. Baudin

7
另一种选择是将记录封装在一个类中。类似于以下内容:

type PersonState = { FirstName : string; LastName : string; }

type Person private (state : PersonState) =

    new (firstName, lastName) = 
        Person({ FirstName = firstName; LastName = lastName })

    member this.WithFirstName value =  
        Person { state with FirstName = value } 

    member this.WithLastName value  =  
        Person { state with LastName = value } 

    member this.FirstName with get () = state.FirstName
    member this.LastName with get () = state.LastName

用途

let JohnDoe = Person("John", "Doe")
let JaneDoe = JohnDoe.WithFirstName "Jane" 
let JaneLastName = JaneDoe.LastName

这种方法避免了显式的克隆和可变性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接