Scalaz(26)- Lens: 函数式不可变对象数据操作方式

  scala中的case class是一种特殊的对象:由编译器(compiler)自动生成字段的getter和setter。如下面的例子:

case class City(name:String, province: String)
case class Address(street: String, zip: String, city: City)
case class Person(name: String, age: Int, phone: String, address: Address)
val Peter = Person("Peter Chan",20,"4806111",Address("13 baoan road","40001",City("ShenZhen","GuangDong")))
                                                  //> Peter  : Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13
                                                  //|  baoan road,40001,City(ShenZhen,GuangDong)))
val John = Person("John Woo",43,"3602011",Address("33 fada road","51001",City("GuangZhou","GuangDong")))
                                                  //> John  : Exercises.LensDemo.Person = Person(John Woo,43,3602011,Address(33 fa
                                                  //| da road,51001,City(GuangZhou,GuangDong)))
val Ming = Person("Fang Ming",23,"3682412",Address("6 jiefang da dao","51012",City("GuangZhou","GuangDong")))
                                                  //> Ming  : Exercises.LensDemo.Person = Person(Fang Ming,23,3682412,Address(6 ji
                                                  //| efang da dao,51012,City(GuangZhou,GuangDong)))

这里我们可以看到:Person是个多层次对象,包含多层嵌入属性对象(multi-layer embeded objects)。如果需要更改Person类型实例中的任何字段时,我们可以直接用行令方式(imperative style):

case class City(var name:String, province: String)
Peter.address.city.name = "DongGuan"
Peter                              //> res0: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 ba
                                   //| oan road,40001,City(DongGuan,GuangDong)))


注意:我必须把case class 属性City的name字段属性变成var,而且这时peter已经转变了(mutated)。既然我们是在函数式编程中,强调的是纯函数代码,即使用不可变对象(immutable objects),那么函数式编程方式的字段操作又可以怎样呢?

val peterDG =           peter.copy(
      address = peter.address.copy(
    city = peter.address.city.copy(name = "DongGuan")))
                                                  //> peterDG  : Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(
                                                  //| 13 baoan road,40001,City(DongGuan,GuangDong)))
peter                                             //> res0: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 ba
                                                  //| oan road,40001,City(ShenZhen,GuangDong)))
peterDG                                           //> res1: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 ba
                                                  //| oan road,40001,City(DongGuan,GuangDong)))


我们可以使用case class的自带函数copy来实现字段操作。但是随着嵌入对象层次的增加,将会产生大量的重复代码。scalaz的Lens type class的主要功能之一就可以解决以上问题。我们先来看看scalaz Lens的用例:

//定义Lens实例
val nameL = Lens.lensu[Person, String]((p,n) => p.copy(name=n), _.name)
                                                  //> nameL  : scalaz.Lens[Exercises.LensDemo.Person,String] = scalaz.LensFunction
                                                  //| s$$anon$5@5f375618
val ageL = Lens.lensu[Person, Int]((p,a) => p.copy(age=a), _.age)
                                                  //> ageL  : scalaz.Lens[Exercises.LensDemo.Person,Int] = scalaz.LensFunctions$$a
                                                  //| non$5@1810399e
val addrL = Lens.lensu[Person,Address]((p,a) => p.copy(address=a), _.address)
                                                  //> addrL  : scalaz.Lens[Exercises.LensDemo.Person,Exercises.LensDemo.Address] 
                                                  //| = scalaz.LensFunctions$$anon$5@32d992b2
val zipL = Lens.lensu[Address,String]((a,z) => a.copy(zip=z), _.zip)
                                                  //> zipL  : scalaz.Lens[Exercises.LensDemo.Address,String] = scalaz.LensFunctio
                                                  //| ns$$anon$5@215be6bb
val cityL = Lens.lensu[Address,City]((a,c) => a.copy(city=c), _.city)
                                                  //> cityL  : scalaz.Lens[Exercises.LensDemo.Address,Exercises.LensDemo.City] = 
                                                  //| scalaz.LensFunctions$$anon$5@4439f31e
val provL = Lens.lensu[City,String]((c,p) => c.copy(province=p), _.province)
                                                  //> provL  : scalaz.Lens[Exercises.LensDemo.City,String] = scalaz.LensFunctions
                                                  //| $$anon$5@5dfcfece
//具体用例
val peter30 = ageL set (peter,30)                 //> peter30  : Exercises.LensDemo.Person = Person(Peter Chan,30,4806111,Address
                                                  //| (13 baoan road,40001,City(ShenZhen,GuangDong)))
val provCap = provL mod (_.toUpperCase,peter.address.city)
                                                  //> provCap  : Exercises.LensDemo.City = City(ShenZhen,GUANGDONG)
provL get provCap                                 //> res2: String = GUANGDONG
val newCity = cityL set (peter.address, City("ChengDu","SiChuan"))
                                                  //> newCity  : Exercises.LensDemo.Address = Address(13 baoan road,40001,City(Ch
                                                  //| engDu,SiChuan))
cityL get newCity                                 //> res3: Exercises.LensDemo.City = City(ChengDu,SiChuan)


可以看到:我们用Lens.lensu构建了上面那些case class的Lens实例,然后分别用get,set,mod对case class的字段进行了读写操作示范。理论上Lens的基本定义大约是这样的:

case class Lens[R,F](
  get: R => F,
  set: (R,F) => R
 )


实际上Lens就是get,set函数的外包(wrapper)。R,F可以分别被理解为记录Record和字段Field类型。get和set是lambda表达式,分别代表:给一个Record,返回Field结果;给一个Record及一个字段值,更新Record中这个字段值后返回新的Record。

我们看看在scalaz中是如何定义Lens的:scalaz/package.scala

//
  // Lens type aliases
  //
  /** A lens that doesn't transform the type of the record. */
  type Lens[A, B] = LensFamily[A, A, B, B]

  // important to define here, rather than at the top-level, to avoid Scala 2.9.2 bug
  object Lens extends LensInstances with LensFunctions {
    def apply[A, B](r: A => Store[B, A]): Lens[A, B] =
      lens(r)
  }

  type @>[A, B] = Lens[A, B]

  //
  // Partial Lens type aliases
  //
  /** A partial lens that doesn't transform the type of the record. */
  type PLens[A, B] = PLensFamily[A, A, B, B]

  // important to define here, rather than at the top-level, to avoid Scala 2.9.2 bug
  object PLens extends PLensInstances with PLensFunctions {
    def apply[A, B](r: A => Option[Store[B, A]]): PLens[A, B] =
      plens(r)
  }

  type @?>[A, B] = PLens[A, B]


Plens是部分作用(partial)Lens,与Lens不同的是Plens的get和set返回的结果都是Option类型的。我们再看看LensFamily的定义:scalaz/Lens.scala

sealed abstract class LensFamily[A1, A2, B1, B2] {
  def run(a: A1): IndexedStore[B1, B2, A2]

  def apply(a: A1): IndexedStore[B1, B2, A2] =
    run(a)
...

如上可以这样理解LensFamily的类型参数LensFamily[R1,R2,F1,F2],分别代表操作前后Record和Field的类型。这样又提供了类型转换(type transformation)操作,可以概括更多类型的Lens。Lens包嵌了个Store类型,我们看看Store的定义:scalaz/StoreT.scala

final case class IndexedStoreT[F[_], +I, A, B](run: (F[A => B], I)) {
...


及scalaz/package.scala

type StoreT[F[_], A, B] = IndexedStoreT[F, A, A, B]
  type IndexedStore[I, A, B] = IndexedStoreT[Id, I, A, B]
  type Store[A, B] = StoreT[Id, A, B]
  // flipped
  type |-->[A, B] = Store[B, A]
  object StoreT extends StoreTInstances with StoreTFunctions {
    def apply[F[_], A, B](r: (F[A => B], A)): StoreT[F, A, B] =
      storeT(r)
  }
  object IndexedStore {
    def apply[I, A, B](f: A => B, i: I): IndexedStore[I, A, B] = IndexedStoreT.indexedStore(i)(f)
  }
  object Store {
    def apply[A, B](f: A => B, a: A): Store[A, B] = StoreT.store(a)(f)
  }


首先,Store就是个函数存储器。与Lens共同使用时它可以存放get和set函数。从Store的存取函数可以分析得见:scalaz/StoreT.scala

...
  def put(a: A)(implicit F: Functor[F]): F[B] =
    F.map(run._1)(_(a))

  def puts(f: I => A)(implicit F: Functor[F]): F[B] =
    put(f(pos))

  def set: F[A => B] =
    run._1

  def pos: I =
    run._2
...


我们看见:如果F = Id的话,put(a) = run._1(a) >>> run._1 是 A => B,函数输入A,返回B,是个setter。pos = run._2 = A,是个getter。但Lens的get,set好像是这样的: get: A => B, set: A => B => A,与Store的A,B刚好相反,不过看看scalaz Lens的构建函数就明白了:scalaz/Lens.scala

trait LensFunctions extends LensFamilyFunctions {

  def lens[A, B](r: A => Store[B, A]): Lens[A, B] = new Lens[A, B] {
    def run(a: A): Store[B, A] = r(a)
  }

  def lensg[A, B](set: A => B => A, get: A => B): Lens[A, B] =
    lens(a => Store(set(a), get(a)))

  def lensu[A, B](set: (A, B) => A, get: A => B): Lens[A, B] =
    lensg(set.curried, get)
...


run(A) >>> Store[B,A], 刚好相反。可以说Store的B就是Record类型,A就是Field类型。所以scalaz版本的Lens包嵌的是个Store[F,R]。我们可以用pos和put把获取get和set。看看LensFamily的get和set源代码:

sealed abstract class LensFamily[A1, A2, B1, B2] {
...
 def get(a: A1): B1 =
    run(a).pos

 def set(a: A1, b: B2): A2 =
    run(a).put(b)
...


实际上即使用Lens过程中为每个字段定义Lens也会涉及到大量的重复代码。但Lens是可组合的(composible),这样我们可以重复利用最基本细小的Lens来组合成更大范围的Lens。scalaz提供了Lens组合函数:

 /** Lenses can be composed */
  def compose[C1, C2](that: LensFamily[C1, C2, A1, A2]): LensFamily[C1, C2, B1, B2] =
    lensFamily(c => {
      val (ac, a) = that.run(c).run
      val (ba, b) = run(a).run
      IndexedStore(ac compose ba, b)
    })

  /** alias for `compose` */
  def <=<[C1, C2](that: LensFamily[C1, C2, A1, A2]): LensFamily[C1, C2, B1, B2] = compose(that)

  def andThen[C1, C2](that: LensFamily[B1, B2, C1, C2]): LensFamily[A1, A2, C1, C2] =
    that compose this

  /** alias for `andThen` */
  def >=>[C1, C2](that: LensFamily[B1, B2, C1, C2]): LensFamily[A1, A2, C1, C2] = andThen(that)


我们试着把Lens组合起来做个示范:

addrL >=> cityL >=> provL get peter               //> res4: String = GuangDong

val peterFromShanXi = addrL >=> cityL >=> provL set(peter,"ShanXi")
                                                  //> peterFromShanXi  : Exercises.LensDemo.Person = Person(Peter Chan,20,4806111
                                                  //| ,Address(13 baoan road,40001,City(ShenZhen,ShanXi)))
peterFromShanXi                                   //> res5: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 b
                                                  //| aoan road,40001,City(ShenZhen,ShanXi)))


Lens可以被转换成State:

 def st: State[A1, B1] =
    State(s => (s, get(s)))

abstract class LensInstances extends LensInstances0 {
  import LensFamily._
  import BijectionT._
  import collection.SeqLike
  import collection.immutable.Stack
  import collection.immutable.Queue

  implicit val lensCategory: LensCategory = new LensCategory {
  }

  /** Lenses may be used implicitly as State monadic actions that get the viewed portion of the state */
  implicit def LensFamilyState[A, B](lens: LensFamily[A, _, B, _]): State[A, B] =
    lens.st
...


这里有一个隐式类型转换函数可以把LensFamily转成State。
scalaz提供了许多函数让我们可以把Lens操作结果转化成State类型:

  /** Modify the portion of the state viewed through the lens and return its new value. */
  def mods(f: B1 => B2): IndexedState[A1, A2, B2] =
    IndexedState(a => {
      val c = run(a)
      val b = f(c.pos)
      (c put b, b)
    })

  /** Modify the portion of the state viewed through the lens and return its new value. */
  def %=(f: B1 => B2): IndexedState[A1, A2, B2] =
    mods(f)

  /** Modify the portion of the state viewed through the lens and return its old value.
   * @since 7.0.2
   */
  def modo(f: B1 => B2): IndexedState[A1, A2, B1] =
    IndexedState(a => {
      val c = run(a)
      val o = c.pos
      (c put f(o), o)
    })

  /** Modify the portion of the state viewed through the lens and return its old value. alias for `modo`
   * @since 7.0.2
   */
  def <%=(f: B1 => B2): IndexedState[A1, A2, B1] =
    modo(f)

  /** Set the portion of the state viewed through the lens and return its new value. */
  def assign(b: => B2): IndexedState[A1, A2, B2] =
    mods(_ => b)

  /** Set the portion of the state viewed through the lens and return its new value. */
  def :=(b: => B2): IndexedState[A1, A2, B2] =
    assign(b)

  /** Set the portion of the state viewed through the lens and return its old value.
   * @since 7.0.2
   */
  def assigno(b: => B2): IndexedState[A1, A2, B1] =
    modo(_ => b)

  /** Set the portion of the state viewed through the lens and return its old value. alias for `assigno`
   * @since 7.0.2
   */
  def <:=(b: => B2): IndexedState[A1, A2, B1] =
    assigno(b)

  /** Modify the portion of the state viewed through the lens, but do not return its new value. */
  def mods_(f: B1 => B2): IndexedState[A1, A2, Unit] =
    IndexedState(a =>
      (mod(f, a), ()))

  /** Modify the portion of the state viewed through the lens, but do not return its new value. */
  def %==(f: B1 => B2): IndexedState[A1, A2, Unit] =
    mods_(f)

  /** Contravariantly map a state action through a lens. */
  def lifts[C](s: IndexedState[B1, B2, C]): IndexedState[A1, A2, C] =
    IndexedState(a => modp(s(_), a))

  def %%=[C](s: IndexedState[B1, B2, C]): IndexedState[A1, A2, C] =
    lifts(s)

  /** Map the function `f` over the lens as a state action. */
  def map[C](f: B1 => C): State[A1, C] =
    State(a => (a, f(get(a))))

  /** Map the function `f` over the value under the lens, as a state action. */
  def >-[C](f: B1 => C): State[A1, C] = map(f)

  /** Bind the function `f` over the value under the lens, as a state action. */
  def flatMap[C](f: B1 => IndexedState[A1, A2, C]): IndexedState[A1, A2, C] =
    IndexedState(a => f(get(a))(a))

  /** Bind the function `f` over the value under the lens, as a state action. */
  def >>-[C](f: B1 => IndexedState[A1, A2, C]): IndexedState[A1, A2, C] =
    flatMap[C](f)

  /** Sequence the monadic action of looking through the lens to occur before the state action `f`. */
  def ->>-[C](f: => IndexedState[A1, A2, C]): IndexedState[A1, A2, C] =
    flatMap(_ => f)


因为State是个Monad,我们可以用for-comprehension来实现行令式编程:

nameL run peter                            //> res6: scalaz.IndexedStore[String,String,Exercises.LensDemo.Person] = Indexe
                                           //| dStoreT((<function1>,Peter Chan))
cityL run peter.address                    //> res7: scalaz.IndexedStore[Exercises.LensDemo.City,Exercises.LensDemo.City,E
                                    //| xercises.LensDemo.Address] = IndexedStoreT((<function1>,City(ShenZhen,GuangDong)))
val updateInfo = for {
  _ <- ageL += 1
  zip <- addrL >=> zipL
  _ <- (addrL >=> zipL) := zip + "A"
  _ <- nameL %= {n => n + " Junior"}
  _ <- (addrL >=> cityL) := City("GuangZhou","GuangDong")
  info <- get
} yield info                             //> updateInfo  : scalaz.IndexedStateT[scalaz.Id.Id,Exercises.LensDemo.Person,E
                                         //| xercises.LensDemo.Person,Exercises.LensDemo.Person] = scalaz.IndexedStateT$
                                         //| $anon$10@74a10858
updateInfo eval peter                    //> res8: scalaz.Id.Id[Exercises.LensDemo.Person] = Person(Peter Chan Junior,21
                                         //| ,4806111,Address(13 baoan road,40001A,City(GuangZhou,GuangDong)))


这是一段比较典型的行令程序。

上面例子的 += 是NumericLens一项操作。NumericLens是这样定义的:

type NumericLens[S, N] = NumericLensFamily[S, S, N]
  val NumericLens: NumericLensFamily.type = NumericLensFamily
  /** Allow the illusion of imperative updates to numbers viewed through a lens */
  case class NumericLensFamily[S1, S2, N](lens: LensFamily[S1, S2, N, N], num: Numeric[N]) {
    def +=(that: N): IndexedState[S1, S2, N] =
      lens %= (num.plus(_, that))

    def -=(that: N): IndexedState[S1, S2, N] =
      lens %= (num.minus(_, that))

    def *=(that: N): IndexedState[S1, S2, N] =
      lens %= (num.times(_, that))
  }

  implicit def numericLensFamily[S1, S2, N: Numeric](lens: LensFamily[S1, S2, N, N]) =
    NumericLens[S1, S2, N](lens, implicitly[Numeric[N]])


scalar还提供了许多标准数据类型的Lens:

trait LensFunctions extends LensFamilyFunctions {

  def lens[A, B](r: A => Store[B, A]): Lens[A, B] = new Lens[A, B] {
    def run(a: A): Store[B, A] = r(a)
  }

  def lensg[A, B](set: A => B => A, get: A => B): Lens[A, B] =
    lens(a => Store(set(a), get(a)))

  def lensu[A, B](set: (A, B) => A, get: A => B): Lens[A, B] =
    lensg(set.curried, get)

  /** The identity lens for a given object */
  def lensId[A]: Lens[A, A] =
    lens(Store(identity, _))

  /** The trivial lens that can retrieve Unit from anything */
  def trivialLens[A]: Lens[A, Unit] =
    lens[A, Unit](a => Store(_ => a, ()))

  /** A lens that discards the choice of right or left from disjunction */
  def codiagLens[A]: Lens[A \/ A, A] =
    lensId[A] ||| lensId[A]

  /** Access the first field of a tuple */
  def firstLens[A, B]: (A, B) @> A =
    lens {
      case (a, b) => Store(x => (x, b), a)
    }

  /** Access the second field of a tuple */
  def secondLens[A, B]: (A, B) @> B =
    lens {
      case (a, b) => Store(x => (a, x), b)
    }

  /** Access the first field of a tuple */
  def lazyFirstLens[A, B]: LazyTuple2[A, B] @> A =
    lens(z => Store(x => LazyTuple2(x, z._2), z._1))

  /** Access the second field of a tuple */
  def lazySecondLens[A, B]: LazyTuple2[A, B] @> B =
    lens(z => Store(x => LazyTuple2(z._1, x), z._2))

  def nelHeadLens[A]: NonEmptyList[A] @> A =
    lens(l => Store(NonEmptyList.nel(_, l.tail), l.head))

  def nelTailLens[A]: NonEmptyList[A] @> List[A] =
    lens(l => Store(NonEmptyList.nel(l.head, _), l.tail))

  /** Access the value at a particular key of a Map **/
  def mapVLens[K, V](k: K): Map[K, V] @> Option[V] =
    lensg(m => ({
      case None => m - k
      case Some(v) => m.updated(k, v)
    }: Option[V] => Map[K, V]), _ get k)
    
  /** Access the value at a particular key of a Map.WithDefault */
  def mapWithDefaultLens[K,V](k: K): Map.WithDefault[K,V] @> V =
    lensg(m => v => m.updated(k,v), m => m(k))

  /** Specify whether a value is in a Set */
  def setMembershipLens[A](a: A): Set[A] @> Boolean =
    lensg(s => b => if (b) s + a else s - a, _.contains(a))

  def applyLens[A, B](k: B => A)(implicit e: Equal[A]): Store[A, B] @> B =
    lens(q => {
      lazy val x = q.pos
      lazy val y = q put x
      Store(b =>
        Store(w => if(e equal (x, w)) b else y, x), y)
    })

  def predicateLens[A]: Store[A, Boolean] @> (A \/ A) =
    lens(q => Store(_ match {
      case -\/(l) => Store(_ => true, l)
      case \/-(r) => Store(_ => false, r)
    }, {
      val x = q.pos
      if(q put x) -\/(x) else \/-(x)
    }))

  def factorLens[A, B, C]: ((A, B) \/ (A, C)) @> (A, B \/ C) =
    lens(e => Store({
      case (a, -\/(b)) => -\/(a, b)
      case (a, \/-(c)) => \/-(a, c)
    }, e match {
      case -\/((a, b)) => (a, -\/(b))
      case \/-((a, c)) => (a, \/-(c))
    }))

  def distributeLens[A, B, C]: (A, B \/ C) @> ((A, B) \/ (A, C)) =
    lens {
      case (a, e) => Store({
        case -\/((aa, bb)) => (aa, -\/(bb))
        case \/-((aa, cc)) => (aa, \/-(cc))
      }, e match {
        case -\/(b) => -\/(a, b)
        case \/-(c) => \/-(a, c)

      })
    }
}


我们下面示范一些用例:

import scala.collection.immutable.Set
val set135 = Set(1,3,5)                           //> set135  : scala.collection.immutable.Set[Int] = Set(1, 3, 5)
Lens.setMembershipLens(3) get set135              //> res0: Boolean = true
Lens.setMembershipLens(7).set(set135,true)        //> res1: Set[Int] = Set(1, 3, 5, 7)
Lens.setMembershipLens(5).set(set135,false)       //> res2: Set[Int] = Set(1, 3)


import scala.collection.immutable.Map
val fooMap = Map(1 -> "A", 2 -> "B")              //> fooMap  : scala.collection.immutable.Map[Int,String] = Map(1 -> A, 2 -> B)
Lens.mapVLens(1) get fooMap                       //> res3: Option[String] = Some(A)
Lens.mapVLens(3).set(fooMap,"C".some)             //> res4: Map[Int,String] = Map(1 -> A, 2 -> B, 3 -> C)
Lens.mapVLens(1).set(fooMap,None)                 //> res5: Map[Int,String] = Map(2 -> B)


以上是针对独立的immutable set和immutable map的操作。与上面的NumericLens示范一样,scalaz还提供了针对包嵌在对象内属性的标准类型操作函数,比如如果上面例子的set和map是case class的字段时该如何操作:

case class Company(name: String, employees: Set[String], ids: Map[Int,String], breaks: Queue[String])
val titleL = Lens.lensu[Company,String]((c,n) => c.copy(name = n), _.name)
                                                  //> titleL  : scalaz.Lens[Exercises.LensDemo.Company,String] = scalaz.LensFunct
                                                  //| ions$$anon$5@385c9627
val empSetLens = Lens.lensu[Company,Set[String]]((c,e) => c.copy(employees = e), _.employees)
                                                  //> empSetLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immut
                                                  //| able.Set[String]] = scalaz.LensFunctions$$anon$5@139982de
val idMapLens = Lens.lensu[Company,Map[Int,String]]((c,i) => c.copy(ids = i), _.ids)
                                                  //> idMapLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immuta
                                                  //| ble.Map[Int,String]] = scalaz.LensFunctions$$anon$5@682b2fa
val breakQLens = Lens.lensu[Company,Queue[String]]((c,b) => c.copy(breaks = b), _.breaks)
                                                  //> breakQLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immut
                                                  //| able.Queue[String]] = scalaz.LensFunctions$$anon$5@217ed35e

val foos = Company("The Foos Co, Ltd.",
    Set("peter","john","susan"),
    Map(1 -> "peter", 2 -> "john", 3 -> "susan"),
    Queue("breakfast","lunch","dinner"))          //> foos  : Exercises.LensDemo.Company = Company(The Foos Co, Ltd.,Set(peter, j
                                                  //| ohn, susan),Map(1 -> peter, 2 -> john, 3 -> susan),Queue(breakfast, lunch, 
                                                  //| dinner))
val newCompany = for {
   n <- titleL
   _ <- titleL := "The New Foo Foo Company"
   _ <- empSetLens -= "john"
   _ <- empSetLens += "kitty"
   _ <- idMapLens -= 2
   _ <- idMapLens += (2 -> "kitty")
   bf <- breakQLens.dequeue
   lc <- breakQLens.dequeue
   dn <- breakQLens.dequeue
   _ <- breakQLens.enqueue(bf)
   _ <- breakQLens.enqueue(lc)
   _ <- breakQLens.enqueue("afternoon tea")
   _ <- breakQLens.enqueue(dn)
   newOne <- get
} yield newOne                                    //> newCompany  : scalaz.IndexedState[Exercises.LensDemo.Company,Exercises.Lens
                                                  //| Demo.Company,Exercises.LensDemo.Company] = scalaz.package$IndexedState$$ano
                                                  //| n$2@2cd76f31
newCompany eval foos                              //> res7: scalaz.Id.Id[Exercises.LensDemo.Company] = Company(The New Foo Foo Co
                                                  //| mpany,Set(peter, susan, kitty),Map(1 -> peter, 3 -> susan, 2 -> kitty),Queu
                                                  //| e(breakfast, lunch, afternoon tea, dinner))


当然,scalaz提供的还有其它类型的Lens,这里就不一一示范了,具体可以参考源代码scalaz/Lens.scala

从以上讨论我们了解到Lens不但解决了多层嵌入属性操作重复代码问题,它还可以进行函数组合,实现重复使用基本Lens组合获取各种不同功能的Lens。最重要的是Lens与State一同使用可以让我们采用行令编程方式来对对象的嵌入属性进行操作。
















从它的构建器传入参数f: A=>B










如果我们对case class的字段进行读写操作的话

你可能感兴趣的:(编程,scala,functional,programmi,scalaz)