zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    【c语言趣味编程100例】爱因斯坦数学题
    【c语言趣味编程100例】求车速
    【c语言】sizeof和strlen函数区别
    Spiral Matrix I, II
    Trapping Rain Water
    Word Ladder**
    Minimum Size Subarray Sum
    Longest Substrings Without Repeating Characters
    Palindrome Linked List
    Container With Most Water
  • 原文地址:https://www.cnblogs.com/hangscer/p/13158292.html
Copyright © 2011-2022 走看看