Scala"更新"不可变对象的最佳实践

sh1*_*1ng 8 functional-programming scala immutability scala-2.10

有了一个可变对象,我可以写出像

var user = DAO.getUser(id)
user.name = "John"
user.email ="john@doe.com"
// logic on user
Run Code Online (Sandbox Code Playgroud)

如果用户是不可变的,那么我需要在每次更改操作时克隆\复制它.

我知道一些方法来执行此操作

  1. 案例类副本
  2. 使用new属性创建新对象的方法(如changeName)

什么是最佳做法?

还有一个问题.是否有任何现有技术可以获得相对于原始对象的"更改"(例如生成更新语句)?

4le*_*x1v 9

你提到的两种方式分别属于功能和OO范例.如果您更喜欢使用抽象数据类型进行功能分解,那么在Scala中,它由case类表示,然后选择copy方法.在我的选项中使用mutators不是一个好习惯,因为这会让你回到Java/C#/ C++生活方式.

另一方面制作ADT案例类似

case class Person(name: String, age: String)
Run Code Online (Sandbox Code Playgroud)

更加明智的是:

class Person(_name: String, _age: String) {
  var name = _name
  var age = _a

  def changeName(newName: String): Unit = { name = newName }
  // ... and so on
}
Run Code Online (Sandbox Code Playgroud)

(不是最好的命令性代码,可以更短,但更清晰).

原因是mutator有另一种方法,只是在每次调用时返回一个新对象:

class Person(val name: String, 
             val age: String) {      
  def changeName(newName: String): Unit = new Person(newName, age)
  // ... and so on
}
Run Code Online (Sandbox Code Playgroud)

但仍然案例类方式更加明确.

如果你继续进行并发/并行编程,你会发现具有不可变值的功能概念要好得多,然后猜测你的对象当前处于什么状态.

更新

感谢senia,忘了提两件事.

镜头
在最基本的层面上,镜头是不可变数据的getter和setter,如下所示:

case class Lens[A,B](get: A => B, set: (A,B) => A) {
  def apply(a: A) = get(a)  
  // ...
}
Run Code Online (Sandbox Code Playgroud)

这就对了.镜头是一个包含两个功能的对象:get和set.get接受A并返回B.set接受A和B并返回一个新的A.很容易看出类型B是A中包含的值.当我们传递一个实例时,我们返回该值.当我们传递A和B来设置时,我们更新A中的值B并返回反映该变化的新A. 为方便起见,get是别名适用.Scalaz Lens案例课程有一个很好的介绍

记录
这一个,ofcause,来自无形的图书馆,并呼吁记录.可扩展记录的实现,建模为关联的HLists.密钥使用单例类型进行编码,并完全确定其对应值的类型(来自github):

object author  extends Field[String]
object title   extends Field[String]
object price   extends Field[Double]
object inPrint extends Field[Boolean]

val book =
  (author -> "Benjamin Pierce") ::
  (title  -> "Types and Programming Languages") ::
  (price  ->  44.11) ::
  HNil

// Read price field
val currentPrice = book.get(price)  // Inferred type is Double
currentPrice == 44.11

// Update price field, relying on static type of currentPrice
val updated = book + (price -> (currentPrice+2.0))

// Add a new field
val extended = updated + (inPrint -> true)
Run Code Online (Sandbox Code Playgroud)

  • 您可以在答案中添加第三种方式:[镜头](https://blog.stackmob.com/2012/02/an-introduction-to-lenses-in-scalaz/).这还不够新答案,但值得一提. (3认同)