Lens in Scala

对于复杂嵌套类的内部字段的更改,在字段可变时的写法为:

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
                     )

现在有两个类Person1Address

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)
知难行易
原创博文,请勿转载
我的又一个博客hangscer.win
原文地址:https://www.cnblogs.com/hangscer/p/13158292.html