对于复杂嵌套类的内部字段的更改,在字段可变时的写法为:
a.b.c += 1
但是对于不可变字段为:
a.copy(
b = a.b.copy(
c = a.b.c + 1))))
//如果修改a.b.c.d.e.f ?
在scala中基本都是使用不可变类型,不可变类型有着避免并发时的数据竞争等等优点。但是在需要更改其内部嵌套的属性字段的值时,很容易写出箭头型
的代码。
解决方案是Lens(透镜),Lens抽象了某类中的某个字段的读取和更新的过程,简单的讲就是Lens代表了某个类的某个字段的setter和getter方法,并且通过组合Lens实例得到功能更强大的实例。
case class Lens[A, B](
set: (A, B) => A,
get: A => B
)
现在有两个类Person1
和Address
case class Person1(name: String, address: Address)
case class Address(mailCode: Int, desc: String)
对于Person中的name字段的Lens实例可以写为:
val person1NameLens = Lens[Person1, String]( //改变name属性
set = (person1, name) => person1.copy(name = name),
get = p => p.name
)
对于Address中的mailCode字段的Lens实例可以写为:
val addressMailCodeLens = Lens[Address, Int](
set = (a, code) => a.copy(mailCode = code),
get = a => a.mailCode
)
对于Person中的Address字段的lens实例可以写为:
val person1AddressLens = Lens[Person1, Address](
set = (p, a) => p.copy(address = a),
get = p => p.address
)
对以上实例的简单运用:
val person1 = Person1(name = "123", address = Address(mailCode = 201202, desc = "PuDong,Shanghai,China"))
assert(person1NameLens.get(person1) == "123")
val person2 = person1NameLens.set(person1, "1234")
assert(person2.name == "1234")
组合基础的Lens实例,得到更复杂的Lens实例,比如修改Person.Address.MailCode字段:
//Person1 ~> Address ~> MailCode ===> Person1 ~> MailCode
val personAddressMailCodeLens = Lens[Person1, Int](
set = (p, mailcode) => person1AddressLens.set(p, addressMailCodeLens.set(person1AddressLens.get(p), mailcode)),
get = p => addressMailCodeLens.get(person1AddressLens.get(p))
)
对personAddressMailCodeLens
实例的简单运用:
val person3 = personAddressMailCodeLens.set(person1, 1111)
assert(personAddressMailCodeLens.get(person3) == 1111)
对于personAddressMailCodeLens
实例,可以将a.b.c
之类的字段更新规则抽象为A~>B~>C ===> A~>C
,将该组合过程抽象为simpleCompose
:
// A ~> B ~> C ===> A~>C
def simpleCompose[A, B, C](outer: Lens[A, B], inner: Lens[B, C]): Lens[A, C] = {
Lens[A, C](
set = (obj, value) => outer.set(obj, inner.set(outer.get(obj), value)),
get = outer.get andThen inner.get
)
}
重构personAddressMailCodeLens
,代码如下:
val personAddressMailCodeLens1: Lens[Person1, Int] =
simpleCompose[Person1, Address, Int](person1AddressLens, addressMailCodeLens)
在工程中,可以使用scalaz或者shapeless等代码库,其中已经实现了Lens等类型。
使用shapeless代码库中的Lens
编写一个personNameLens
,基于shapeless,可以省略一般的模板代码:
import shapeless._
val personNameLens = lens[Person1] >> 'name
val person1 = Person1(name = "123", address = Address(mailCode = 201202, desc = "PuDong,Shanghai,China"))
val newPerson = personNameLens.modify(person1)(_ + "7890")
val name: String = personNameLens.get(newPerson)
assert(name == "1237890")
对于深层次的嵌套类型的字段修改,默认也不需要各种compse函数来组合lens实例:
import shapeless._
val personAddressMailCodeLens = lens[Person1] >> 'address >> 'mailCode
val mailCode: Int = personAddressMailCodeLens.get(person1)
assert(mailCode == 201202)