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)))
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)
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)) { ...
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) }
... 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 ...
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) ...
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) ...
/** 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)
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)))
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 ...
/** 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)
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)
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))
从以上讨论我们了解到Lens不但解决了多层嵌入属性操作重复代码问题,它还可以进行函数组合,实现重复使用基本Lens组合获取各种不同功能的Lens。最重要的是Lens与State一同使用可以让我们采用行令编程方式来对对象的嵌入属性进行操作。
从它的构建器传入参数f: A=>B
如果我们对case class的字段进行读写操作的话