Scalaz(26)- Lens: 函數式不可變對象數據操作方式

来源:http://www.cnblogs.com/tiger-xc/archive/2016/01/30/5170723.html
-Advertisement-
Play Games

scala中的case class是一種特殊的對象:由編譯器(compiler)自動生成欄位的getter和setter。如下麵的例子: 1 case class City(name:String, province: String) 2 case class Address(street: Str


  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]])

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)

      })
    }
}

我們下麵示範一些用例:

 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一同使用可以讓我們採用行令編程方式來對對象的嵌入屬性進行操作。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一個成熟的大型網站(如淘寶、京東等)的系統架構並不是開始設計就具備完整的高性能、高可用、安全等特性,它總是隨著用戶量的增加,業務功能的擴展逐漸演變完善的,在這個過程中,開發模式、技術架構、設計思想也發生了很大的變化,就連技術人員也從幾個人發展到一個部門甚至一條產品線。 (歡迎大家訪問我的個人網站:工
  • 話題從重用開始說起: 最基本的重用,重用一個方法,被重用的邏輯被抽取封裝成為方法,之後我們把方法當成一種工具來使用(處理數據,輸入輸出,或者改變狀態)。 來到了面向對象的時代,如果這個方法出現父類上面就是繼承,如果這個方法出現在其他對象上就是代理,如果子類想要重用父類的介面又不想重用實現那麼就是多態
  • 頭文件: <cfloat> (float.h) 浮點類型的特性 這個頭文件為特殊系統和編譯器的實現描述了浮點類型的特征. 一個浮點數包含四個元素: 一個標誌(a sign):正或負; 一個基數(a base):這表示一串不同的數字能夠被一個個單一的數字所表示(2 就是二進位(binary),10就是
  • 程式產生一個1-100的隨機數,用戶嘗試儘可能少的次數猜出這個數。 #include<stdio.h> #include<stdlib.h> #include<time.h> #define MAX_NUMBER 100 //external variable int secret_number;
  • 本文藉鑒於他人 學習java的第一步就要搭建java的學習環境,首先是要安裝JDK,JDK安裝好之後,還需要在電腦上配置"JAVA_HOME"、"path"、"classpath" 這三個環境變數才能夠把java的開發環境搭建好。在沒安裝過jdk的環境下,path環境變數是系統變數,本來存在的,而J
  • 1. 文本264stream_720_2zhen.txt中存儲的數據如圖1所示 圖 1 要導入的數據截圖 2. MATLAB中讀入數據的命令,由於數據之間間隔2個空格,所以fscanf格式中[‘%x’ ]中括弧中加入了兩個空格 %read data from txt fid= fopen('264s
  • chrome開發總結(交互/許可權/存儲) 爬蟲 [TOC] 標簽(空格分隔): 雜亂之地 經過一翻折騰。還是選擇了chrome來做爬蟲。主要是為瞭解決一些ajax載入的問題以及代理的問題。 1.chrome 配置文件 manifest.json 上面這些不是必須的。把自己需要的加上就可以了。基本上後
  • JBOSS安裝與配置搭建本地項目環境 一、JDK的安裝 首先安裝JDK,配置環境變數(PATH,CLASSPATH,JAVA_HOME)。 可以參照:Windows環境下JDK安裝與環境變數配置 二、Jboss的介紹 JBOSS是EJB的伺服器,就像Tomcat是JSP伺服器一樣,就是伺服器的一種。
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...