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

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

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

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

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

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

1 al peterDG =           peter.copy(
2       address = peter.address.copy(
3     city = peter.address.city.copy(name = "DongGuan")))
4                                                   //> peterDG  : Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(
5                                                   //| 13 baoan road,40001,City(DongGuan,GuangDong)))
6 peter                                             //> res0: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 ba
7                                                   //| oan road,40001,City(ShenZhen,GuangDong)))
8 peterDG                                           //> res1: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 ba
9                                                   //| oan road,40001,City(DongGuan,GuangDong)))

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

 1 //定义Lens实例
 2 val nameL = Lens.lensu[Person, String]((p,n) => p.copy(name=n), _.name)
 3                                                   //> nameL  : scalaz.Lens[Exercises.LensDemo.Person,String] = scalaz.LensFunction
 4                                                   //| s$$anon$5@5f375618
 5 val ageL = Lens.lensu[Person, Int]((p,a) => p.copy(age=a), _.age)
 6                                                   //> ageL  : scalaz.Lens[Exercises.LensDemo.Person,Int] = scalaz.LensFunctions$$a
 7                                                   //| non$5@1810399e
 8 val addrL = Lens.lensu[Person,Address]((p,a) => p.copy(address=a), _.address)
 9                                                   //> addrL  : scalaz.Lens[Exercises.LensDemo.Person,Exercises.LensDemo.Address] 
10                                                   //| = scalaz.LensFunctions$$anon$5@32d992b2
11 val zipL = Lens.lensu[Address,String]((a,z) => a.copy(zip=z), _.zip)
12                                                   //> zipL  : scalaz.Lens[Exercises.LensDemo.Address,String] = scalaz.LensFunctio
13                                                   //| ns$$anon$5@215be6bb
14 val cityL = Lens.lensu[Address,City]((a,c) => a.copy(city=c), _.city)
15                                                   //> cityL  : scalaz.Lens[Exercises.LensDemo.Address,Exercises.LensDemo.City] = 
16                                                   //| scalaz.LensFunctions$$anon$5@4439f31e
17 val provL = Lens.lensu[City,String]((c,p) => c.copy(province=p), _.province)
18                                                   //> provL  : scalaz.Lens[Exercises.LensDemo.City,String] = scalaz.LensFunctions
19                                                   //| $$anon$5@5dfcfece
20 //具体用例
21 val peter30 = ageL set (peter,30)                 //> peter30  : Exercises.LensDemo.Person = Person(Peter Chan,30,4806111,Address
22                                                   //| (13 baoan road,40001,City(ShenZhen,GuangDong)))
23 val provCap = provL mod (_.toUpperCase,peter.address.city)
24                                                   //> provCap  : Exercises.LensDemo.City = City(ShenZhen,GUANGDONG)
25 provL get provCap                                 //> res2: String = GUANGDONG
26 val newCity = cityL set (peter.address, City("ChengDu","SiChuan"))
27                                                   //> newCity  : Exercises.LensDemo.Address = Address(13 baoan road,40001,City(Ch
28                                                   //| engDu,SiChuan))
29 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组合起来做个示范:

1 addrL >=> cityL >=> provL get peter               //> res4: String = GuangDong
2 
3 val peterFromShanXi = addrL >=> cityL >=> provL set(peter,"ShanXi")
4                                                   //> peterFromShanXi  : Exercises.LensDemo.Person = Person(Peter Chan,20,4806111
5                                                   //| ,Address(13 baoan road,40001,City(ShenZhen,ShanXi)))
6 peterFromShanXi                                   //> res5: Exercises.LensDemo.Person = Person(Peter Chan,20,4806111,Address(13 b
7                                                   //| 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来实现行令式编程:

 1 nameL run peter                            //> res6: scalaz.IndexedStore[String,String,Exercises.LensDemo.Person] = Indexe
 2                                            //| dStoreT((<function1>,Peter Chan))
 3 cityL run peter.address                    //> res7: scalaz.IndexedStore[Exercises.LensDemo.City,Exercises.LensDemo.City,E
 4                                     //| xercises.LensDemo.Address] = IndexedStoreT((<function1>,City(ShenZhen,GuangDong)))
 5 val updateInfo = for {
 6   _ <- ageL += 1
 7   zip <- addrL >=> zipL
 8   _ <- (addrL >=> zipL) := zip + "A"
 9   _ <- nameL %= {n => n + " Junior"}
10   _ <- (addrL >=> cityL) := City("GuangZhou","GuangDong")
11   info <- get
12 } yield info                             //> updateInfo  : scalaz.IndexedStateT[scalaz.Id.Id,Exercises.LensDemo.Person,E
13                                          //| xercises.LensDemo.Person,Exercises.LensDemo.Person] = scalaz.IndexedStateT$
14                                          //| $anon$10@74a10858
15 updateInfo eval peter                    //> res8: scalaz.Id.Id[Exercises.LensDemo.Person] = Person(Peter Chan Junior,21
16                                          //| ,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]])

scalaz还提供了许多标准数据类型的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)

      })
    }
}

我们下面示范一些用例:

 1 import scala.collection.immutable.Set
 2 val set135 = Set(1,3,5)                           //> set135  : scala.collection.immutable.Set[Int] = Set(1, 3, 5)
 3 Lens.setMembershipLens(3) get set135              //> res0: Boolean = true
 4 Lens.setMembershipLens(7).set(set135,true)        //> res1: Set[Int] = Set(1, 3, 5, 7)
 5 Lens.setMembershipLens(5).set(set135,false)       //> res2: Set[Int] = Set(1, 3)
 6 
 7 
 8 import scala.collection.immutable.Map
 9 val fooMap = Map(1 -> "A", 2 -> "B")              //> fooMap  : scala.collection.immutable.Map[Int,String] = Map(1 -> A, 2 -> B)
10 Lens.mapVLens(1) get fooMap                       //> res3: Option[String] = Some(A)
11 Lens.mapVLens(3).set(fooMap,"C".some)             //> res4: Map[Int,String] = Map(1 -> A, 2 -> B, 3 -> C)
12 Lens.mapVLens(1).set(fooMap,None)                 //> res5: Map[Int,String] = Map(2 -> B)

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

 1 case class Company(name: String, employees: Set[String], ids: Map[Int,String], breaks: Queue[String])
 2 val titleL = Lens.lensu[Company,String]((c,n) => c.copy(name = n), _.name)
 3                                                   //> titleL  : scalaz.Lens[Exercises.LensDemo.Company,String] = scalaz.LensFunct
 4                                                   //| ions$$anon$5@385c9627
 5 val empSetLens = Lens.lensu[Company,Set[String]]((c,e) => c.copy(employees = e), _.employees)
 6                                                   //> empSetLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immut
 7                                                   //| able.Set[String]] = scalaz.LensFunctions$$anon$5@139982de
 8 val idMapLens = Lens.lensu[Company,Map[Int,String]]((c,i) => c.copy(ids = i), _.ids)
 9                                                   //> idMapLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immuta
10                                                   //| ble.Map[Int,String]] = scalaz.LensFunctions$$anon$5@682b2fa
11 val breakQLens = Lens.lensu[Company,Queue[String]]((c,b) => c.copy(breaks = b), _.breaks)
12                                                   //> breakQLens  : scalaz.Lens[Exercises.LensDemo.Company,scala.collection.immut
13                                                   //| able.Queue[String]] = scalaz.LensFunctions$$anon$5@217ed35e
14 
15 val foos = Company("The Foos Co, Ltd.",
16     Set("peter","john","susan"),
17     Map(1 -> "peter", 2 -> "john", 3 -> "susan"),
18     Queue("breakfast","lunch","dinner"))          //> foos  : Exercises.LensDemo.Company = Company(The Foos Co, Ltd.,Set(peter, j
19                                                   //| ohn, susan),Map(1 -> peter, 2 -> john, 3 -> susan),Queue(breakfast, lunch, 
20                                                   //| dinner))
21 val newCompany = for {
22    n <- titleL
23    _ <- titleL := "The New Foo Foo Company"
24    _ <- empSetLens -= "john"
25    _ <- empSetLens += "kitty"
26    _ <- idMapLens -= 2
27    _ <- idMapLens += (2 -> "kitty")
28    bf <- breakQLens.dequeue
29    lc <- breakQLens.dequeue
30    dn <- breakQLens.dequeue
31    _ <- breakQLens.enqueue(bf)
32    _ <- breakQLens.enqueue(lc)
33    _ <- breakQLens.enqueue("afternoon tea")
34    _ <- breakQLens.enqueue(dn)
35    newOne <- get
36 } yield newOne                                    //> newCompany  : scalaz.IndexedState[Exercises.LensDemo.Company,Exercises.Lens
37                                                   //| Demo.Company,Exercises.LensDemo.Company] = scalaz.package$IndexedState$$ano
38                                                   //| n$2@2cd76f31
39 newCompany eval foos                              //> res7: scalaz.Id.Id[Exercises.LensDemo.Company] = Company(The New Foo Foo Co
40                                                   //| mpany,Set(peter, susan, kitty),Map(1 -> peter, 3 -> susan, 2 -> kitty),Queu
41                                                   //| e(breakfast, lunch, afternoon tea, dinner))

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏函数式编程语言及工具

Akka(24): Stream:从外部系统控制数据流-control live stream from external system

 在数据流应用的现实场景中常常会遇到与外界系统对接的需求。这些外部系统可能是Actor系统又或者是一些其它类型的系统。与这些外界系统对接的意思是在另一个线程...

20910
来自专栏博客园

WebAPI返回JSON

web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返回为json呢,下面就介绍两种方法:  方法一:(改配置法)  ...

2062
来自专栏石奈子的Java之路

原 荐 SpringBoot 2.0 系列0

2163
来自专栏大内老A

ASP.NET Core的配置(3): 将配置绑定为对象[上篇]

出于编程上的便利,我们通常不会直接利用ConfigurationBuilder创建的Configuration对象读取某个单一配置项的值,而是倾向于将一组相关的...

1896
来自专栏码匠的流水账

kafka0.8生产者异常处理

本文简单解析一下kafka0.8.2.2版本中的java producer的异常处理。

601
来自专栏.NET开发那点事

使用CodeDom动态生成类型

.NET 3.5的时候加入了匿名类型这个特性,我们可以直接使用 new {name="abc"} 来直接生成一个对象。这个特性现在应用的地方很多,比如dappe...

553
来自专栏WindCoder

数据统计第一弹-按时/天/周/月补全某一段时间的数据-Java核心逻辑

本代码均结合之前的发布的DateUtil使用,之后的mysql查询部分看心情发布,就这么任性~ ~

361
来自专栏大内老A

深入理解C# 3.x的新特性(1): Anonymous Type

在C#3.0中,引入了一个新的Feature:Anonymous Method,允许我们已Inline的方式来定义Delegate,为Developer在Cod...

2628
来自专栏魂祭心

原 状态机 搜索字符串中的特定占位符

2907
来自专栏MelonTeam专栏

KVO实现原理

概览 本文分为两个大的方面。一、kvo的简单使用场景。二、kvo的来龙去脉,讲讲苹果的实现。 KVO 使用方法,和常用场景。 Key-value obser...

22710

扫码关注云+社区