虽然很多人都称赞不变性,但我发现在主流编程中很难维护。根据我的经验,程序员迟早会让字段再次可变,以避免重构必须将更新的对象与返回值一起传递的大段代码。
Scala 对复制构造函数有一些支持,但我知道更新复杂对象结构没有令人满意的解决方案。我可能错过了什么。
我尝试过的最好的实现是 Haskell 中的 data-lens。然而,Haskell 很难学。Java 或 Scala 等流行的跨平台编程语言有哪些选择?
虽然很多人都称赞不变性,但我发现在主流编程中很难维护。根据我的经验,程序员迟早会让字段再次可变,以避免重构必须将更新的对象与返回值一起传递的大段代码。
Scala 对复制构造函数有一些支持,但我知道更新复杂对象结构没有令人满意的解决方案。我可能错过了什么。
我尝试过的最好的实现是 Haskell 中的 data-lens。然而,Haskell 很难学。Java 或 Scala 等流行的跨平台编程语言有哪些选择?
镜头确实不需要语言级别的支持——当然,根据语言的属性,它们可能或多或少有用,语法的清晰度将取决于语言的特性。
正如我在上面的评论中提到的,Scala 有很好的镜头库,即使该语言本身没有(并且可以说不应该)提供它们。例如,假设我们有以下类:
case class Email(user: String, domain: String)
case class Contact(email: Email, web: String)
case class Person(name: String, contact: Contact)
还有一个例子:
val foo = Person(
"Foo McBar",
Contact(Email("foo", "mcbar.com"), "http://mcbar.com/foo")
)
使用Shapeless,您可以编写以下内容(请注意,在即将到来的 2.0 版本中,不再需要同构样板):
import shapeless._, Nat._
implicit val emailIso = Iso.hlist(Email.apply _, Email.unapply _)
implicit val contactIso = Iso.hlist(Contact.apply _, Contact.unapply _)
implicit val personIso = Iso.hlist(Person.apply _, Person.unapply _)
接着:
val emailDomainLens = Lens[Contact] >> _1 >> _1
现在 Foo McBar 可以轻松更改他或她的电子邮件域:
scala> println(emailHostLens.set(foo)("mcbar.org"))
Person(Foo McBar,Contact(Email(foo,mcbar.com),mcbar.org))
这都是普通的 Scala——Shapeless (1.2.4) 的当前版本不使用宏或编译器插件等,并且可以在 Scala 2.9 上运行。如果我们愿意使用 Scala 2.10 的宏,我们可以获得更好的语法和更少的样板:
scala> import rillit._
import rillit._
scala> println(Lenser[Person].contact.email.domain.set(foo)("mcbar.org"))
Person(Foo McBar,Contact(Email(foo,mcbar.org),http://mcbar.com/foo))
这使用了Rillit ,这是一个由Aki Saarinen开发的概念验证镜头库(后来由我改编)。
所有这些东西都可以在 Java 中完成,尽管语法不太可能那么干净。事实上,我确信 Java 有镜头库,尽管我从未见过或使用过任何镜头库,而且相对缺乏对不可变数据类型的重视意味着大多数 Java 开发人员永远不需要或想要镜头。
Octarine中有镜头,用于组合记录键以创建数据结构的路径:
Record testRecord = $$(
name.of("Arthur Putey"),
age.of(43),
address.of(
addressLines.of("23 Acacia Avenue", "Sunderland", "VB6 5UX")
)
);
Lens<Record, String> secondLineOfAddress = address.assertPresent()
.join(addressLines.assertPresent())
.join(Lens.intoPVector(1));
assertThat(secondLineOfAddress.get(testRecord), equalTo("Sunderland"));
assertThat(secondLineOfAddress.set(testRecord, "Cirencester"), equalTo($$(
name.of("Arthur Putey"),
age.of(43),
address.of(Record.of(
addressLines.of("23 Acacia Avenue", "Cirencester", "VB6 5UX")
))
)));