The release of Scalaz 7 is getting closer and closer every day and many projects are migrating to new version of this awesome library. This post is intended to help with migration of Scalaz’ lenses. As it is assumed that you are already familiar with lenses some basic changes and additions will be demonstrated by examples.
For our examples we will use the following simple model:
The type signature of Lens is different (if not completely rewritten :)) in Scalaz 7:
What a Lens is in Scalaz 6
1
caseclassLens[A,B](get:A=>B,set:(A,B)=>A)
What a Lens is in Scalaz 7
1234567891011121314151617181920212223
typeLens[A, B]=LensT[Id, A, B]objectLensextendsLensTFunctionswithLensTInstances{defapply[A, B](r:A=>Store[B, A]):Lens[A, B]=lens(r)}// A The type of the record// B The type of the fieldsealedtraitLensT[F[+_], A, B]{defrun(a:A):F[Store[B, A]]defapply(a:A):F[Store[B, A]]=run(a)...}typeStore[A, B]=StoreT[Id, A, B]// flippedtype|-->[A, B]=Store[B, A]objectStore{defapply[A, B](f:A=>B,a:A):Store[A, B]=StoreT.store(a)(f)}
The constructor of Lens have been completely changed in Scalaz 7. Nevertheless there is a Lens.lensu constructor that takes the same but flipped arguments as the old one. So let’s create the lenses for user name, address and zip code (through address):
Now we can read and update user fields with appropriate lenses as before:
12345
scala>nameL.get(user)//reading user name (nameL(user) is not identical to nameL.get(user) anymore)res0:scalaz.Id.Id[String]=Nadscala>addrL.set(user,Address("Empty",0))// updating user addressres1:scalaz.Id.Id[User]=User(Nad,Address(Empty,0))
In contrast to set parameters of mod function have been flipped:
12
scala>zipL.mod((1+),user.address)// modifying user zip code through address (user.address)res2:scalaz.Id.Id[Address]=Address(Sallad,79072)
There is a useful addition to mod function called =>=:
In addition to compose and andThen functions there are aliases <=< and >=> (respectively):
1234567891011121314
scala>zipLcomposeaddrLres4:scalaz.LensT[scalaz.Id.Id,User,Int]=scalaz.LensTFunctions$$anon$5@51557949scala>zipL<=<addrL// just alias to compose functionres5:scalaz.LensT[scalaz.Id.Id,User,Int]=scalaz.LensTFunctions$$anon$5@3f1cf257scala>valzipThroughUserL=addrLandThenzipL// composing two lenses (Lens[User, Address] andThen Lens[Address, Int] = Lens[User, Int])zipThroughUserL:scalaz.LensT[scalaz.Id.Id,User,Int]=scalaz.LensTFunctions$$anon$5@5c921914scala>zipThroughUserL.mod((_-1),user)// modifying user zip code through user itself with composed lensesres6:scalaz.Id.Id[User]=User(Nad,Address(Sallad,79070))scala>(addrL>=>zipL).mod((_-1),user)// the same as two previous linesres7:scalaz.Id.Id[User]=User(Nad,Address(Sallad,79070))
Homomorphism of lens categories lets us to get a value as of Option[B] type (where B is the type of the field):
A homomorphism of lens categories
12345
scala>nameLgetuser// result is of type Stringres8:scalaz.Id.Id[String]=Nadscala>~nameLgetuser// but here the result is of type Option[String]!res9:scalaz.Id.Id[Option[String]]=Some(Nad)
Using lens as a State monad (including map and flatMap as >- and >>- respectively):
Set the portion of the state viewed through the lens and return its new value
1234567891011
scala>valzipState=for{|x<-zipL|_<-zipL:=x+1|}yieldxzipState:scalaz.StateT[scalaz.Id.Id,Address,Int]=scalaz.StateT$$anon$7@346d9067scala>zipState.run(user.address)res10:(Address,Int)=(Address(Sallad,79072),79071)scala>zipState.eval(user.address)// discard the final stateres11:scalaz.Id.Id[Int]=79071
Modify the portion of the state viewed through the lens and return its new value
Map the function over the value under the lens, as a state action
12
scala>(nameL>-(_.toUpperCase))runuser// >- is an alias for mapres14:(User,java.lang.String)=(User(Nad,Address(Sallad,79071)),NAD)
Bind the function over the value under the lens, as a state action
1234
valupNameL:Lens[User, String]=Lens.lensu((u,newName)=>u.copy(name=newName.toUpperCase),_.name.toUpperCase)// yet another lens for user namescala>(nameL>>-(_=>upNameL))runuser// >>- is an alias for flatMapres15:(User,String)=(User(Nad,Address(Sallad,79071)),NAD)
Sequence the monadic action of looking through the lens to occur before the state action
12
scala>nameL->>-upNameLrunuser// uses flatMap (>>-) for sequencing monadic actionsres16:(User,String)=(User(Nad,Address(Sallad,79071)),NAD)
Looking through the examples above it is not difficult to see that there is a bunch of new methods (and nice aliases) especially for working with a lens as a state monad. Since type complexity is under the hood working with lenses is even easier now than before. Waiting for Scalaz 7 release!