learning Scalaz: day 11

in

Hey there. There's an updated html5 book version, if you want.

Darren Hester for openphoto.net

Yesterday we looked at Reader monad as a way of abstracting configuration, and introduced monad transformers.

Today, let's look at lenses. It's a hot topic many people are talking, and looks like it has clear use case.

Go turtle go

Seth Tisue (@SethTisue) gave a talk on shapeless lenses at Scalathon this year. I missed the talk, but I am going to borrow his example.

scala> case class Point(x: Double, y: Double)
defined class Point
 
scala> case class Color(r: Byte, g: Byte, b: Byte)
defined class Color
 
scala> case class Turtle(
         position: Point,
         heading: Double,
         color: Color)
 
scala> Turtle(Point(2.0, 3.0), 0.0,
         Color(255.toByte, 255.toByte, 255.toByte))
res0: Turtle = Turtle(Point(2.0,3.0),0.0,Color(-1,-1,-1))

Now without breaking the immutability, we want to move the turtle forward.

scala> case class Turtle(position: Point, heading: Double, color: Color) {
         def forward(dist: Double): Turtle =
           copy(position =
             position.copy(
               x = position.x + dist * math.cos(heading),
               y = position.y + dist * math.sin(heading)
           ))
       }
defined class Turtle
 
scala> Turtle(Point(2.0, 3.0), 0.0,
         Color(255.toByte, 255.toByte, 255.toByte))
res10: Turtle = Turtle(Point(2.0,3.0),0.0,Color(-1,-1,-1))
 
scala> res10.forward(10)
res11: Turtle = Turtle(Point(12.0,3.0),0.0,Color(-1,-1,-1))

To update the child data structure, we need to nest copy call. To quote from Seth's example again:

// imperative
a.b.c.d.e += 1
 
// functional
a.copy(
  b = a.b.copy(
    c = a.b.c.copy(
      d = a.b.c.d.copy(
        e = a.b.c.d.e + 1
))))

The idea is to get rid of unnecessary copy calls.

Lens

Let's look at Lens in Scalaz7:

  type Lens[A, B] = LensT[Id, A, B]
 
  object Lens extends LensTFunctions with LensTInstances {
    def apply[A, B](r: A => Store[B, A]): Lens[A, B] =
      lens(r)
  }

Lens is a type alias for LensT[Id, A, B] like many other typeclasses.

LensT

LensT looks like this:

import StoreT._
import Id._
 
sealed trait LensT[F[+_], A, B] {
  def run(a: A): F[Store[B, A]]
  def apply(a: A): F[Store[B, A]] = run(a)
  ...
}
 
object LensT extends LensTFunctions with LensTInstances {
  def apply[F[+_], A, B](r: A => F[Store[B, A]]): LensT[F, A, B] =
    lensT(r)
}
 
trait LensTFunctions {
  import StoreT._
 
  def lensT[F[+_], A, B](r: A => F[Store[B, A]]): LensT[F, A, B] = new LensT[F, A, B] {
    def run(a: A): F[Store[B, A]] = r(a)
  }
 
  def lensgT[F[+_], A, B](set: A => F[B => A], get: A => F[B])(implicit M: Bind[F]): LensT[F, A, B] =
    lensT(a => M(set(a), get(a))(Store(_, _)))
  def lensg[A, B](set: A => B => A, get: A => B): Lens[A, B] =
    lensgT[Id, A, B](set, get)
  def lensu[A, B](set: (A, B) => A, get: A => B): Lens[A, B] =
    lensg(set.curried, get)
  ...
}

Store

What's a Store?

  type Store[A, B] = StoreT[Id, A, B]
  // flipped
  type |-->[A, B] = Store[B, A]
  object Store {
    def apply[A, B](f: A => B, a: A): Store[A, B] = StoreT.store(a)(f)
  }

It looks like a wrapper for setter A => B => A and getter A => B.

Using Lens

Let's define turtlePosition and pointX:

scala> val turtlePosition = Lens.lensu[Turtle, Point] (
         (a, value) => a.copy(position = value),
         _.position
       )
turtlePosition: scalaz.Lens[Turtle,Point] = scalaz.LensTFunctions$$anon$5@421dc8c8
 
scala> val pointX = Lens.lensu[Point, Double] (
         (a, value) => a.copy(x = value),
         _.x
       )
pointX: scalaz.Lens[Point,Double] = scalaz.LensTFunctions$$anon$5@30d31cf9

Next we can take advantage of a bunch of operators introduced in Lens. Similar to monadic function composition we saw in Kleisli, LensT implements compose (symbolic alias <=<), and andThen (symbolic alias >=>). I personally think >=> looks cool, so let's use that to define turtleX:

scala> val turtleX = turtlePosition >=> pointX
turtleX: scalaz.LensT[scalaz.Id.Id,Turtle,Double] = scalaz.LensTFunctions$$anon$5@11b35365

The type makes sense since it's going form Turtle to Double. Using get method we can get the value:

scala> val t0 = Turtle(Point(2.0, 3.0), 0.0,
                  Color(255.toByte, 255.toByte, 255.toByte))
t0: Turtle = Turtle(Point(2.0,3.0),0.0,Color(-1,-1,-1))
 
scala> turtleX.get(t0)
res16: scalaz.Id.Id[Double] = 2.0

Success! Setting a new value using set method should return a new Turtle:

scala> turtleX.set(t0, 5.0)
res17: scalaz.Id.Id[Turtle] = Turtle(Point(5.0,3.0),0.0,Color(-1,-1,-1))

This works too. What if I want to get the value, apply it to some function, and set using the result? mod does exactly that:

scala> turtleX.mod(_ + 1.0, t0)
res19: scalaz.Id.Id[Turtle] = Turtle(Point(3.0,3.0),0.0,Color(-1,-1,-1))

There's a symbolic variation to mod that's curried called =>=. This generates Turtle => Turtle function:

scala> val incX = turtleX =>= {_ + 1.0}
incX: Turtle => scalaz.Id.Id[Turtle] = <function1>
 
scala> incX(t0)
res26: scalaz.Id.Id[Turtle] = Turtle(Point(3.0,3.0),0.0,Color(-1,-1,-1))

We are now describing change of internal values upfront and passing in the actual value at the end. Does this remind you of something?

Lens as a State monad

That sounds like a state transition to me. In fact Lens and State I think are good match since they are sort of emulating imperative programming on top of immutable data structure. Here's another way of writing incX:

scala> val incX = for {
         x <- turtleX %= {_ + 1.0}
       } yield x
incX: scalaz.StateT[scalaz.Id.Id,Turtle,Double] = scalaz.StateT$$anon$7@38e61ffa
 
scala> incX(t0)
res28: (Turtle, Double) = (Turtle(Point(3.0,3.0),0.0,Color(-1,-1,-1)),3.0)

%= method takes a function Double => Double and returns a State monad that expresses the change.

Let's make turtleHeading and turtleY too:

scala> val turtleHeading = Lens.lensu[Turtle, Double] (
         (a, value) => a.copy(heading = value),
         _.heading
       )
turtleHeading: scalaz.Lens[Turtle,Double] = scalaz.LensTFunctions$$anon$5@44fdec57
 
scala> val pointY = Lens.lensu[Point, Double] (
         (a, value) => a.copy(y = value),
         _.y
       )
pointY: scalaz.Lens[Point,Double] = scalaz.LensTFunctions$$anon$5@ddede8c
 
scala> val turtleY = turtlePosition >=> pointY

This is no fun because it feels boilerplatey. But, we can now move turtle forward! Instead of general %=, Scalaz even provides sugars like += for Numeric lenses. Here's what I mean:

scala> def forward(dist: Double) = for {
         heading <- turtleHeading
         x <- turtleX += dist * math.cos(heading)
         y <- turtleY += dist * math.sin(heading)
       } yield (x, y)
forward: (dist: Double)scalaz.StateT[scalaz.Id.Id,Turtle,(Double, Double)]
 
scala> forward(10.0)(t0)
res31: (Turtle, (Double, Double)) = (Turtle(Point(12.0,3.0),0.0,Color(-1,-1,-1)),(12.0,3.0))
 
scala> forward(10.0) exec (t0)
res32: scalaz.Id.Id[Turtle] = Turtle(Point(12.0,3.0),0.0,Color(-1,-1,-1))

Now we have implemented forward function without using a single copy(position = ...). It's nice but we still needed some prep work to get here, so there is some tradeoff. Lens defines a lot more methods, but the above should be a good starter. Let's see them all again:

sealed trait LensT[F[+_], A, B] {
  def get(a: A)(implicit F: Functor[F]): F[B] =
    F.map(run(a))(_.pos)
  def set(a: A, b: B)(implicit F: Functor[F]): F[A] =
    F.map(run(a))(_.put(b))
  /** Modify the value viewed through the lens */
  def mod(f: B => B, a: A)(implicit F: Functor[F]): F[A] = ...
  def =>=(f: B => B)(implicit F: Functor[F]): A => F[A] =
    mod(f, _)
  /** Modify the portion of the state viewed through the lens and return its new value. */
  def %=(f: B => B)(implicit F: Functor[F]): StateT[F, A, B] =
    mods(f)
  /** Lenses can be composed */
  def compose[C](that: LensT[F, C, A])(implicit F: Bind[F]): LensT[F, C, B] = ...
  /** alias for `compose` */
  def <=<[C](that: LensT[F, C, A])(implicit F: Bind[F]): LensT[F, C, B] = compose(that)
  def andThen[C](that: LensT[F, B, C])(implicit F: Bind[F]): LensT[F, A, C] =
    that compose this
  /** alias for `andThen` */
  def >=>[C](that: LensT[F, B, C])(implicit F: Bind[F]): LensT[F, A, C] = andThen(that)
}

Lens laws

Seth says:

lens laws are common sense

(0. if I get twice, I get the same answer)
1. if I get, then set it back, nothing changes.
2. if I set, then get, I get what I set.
3. if I set twice then get, I get the second thing I set.

He's right. These are common sense. Here how Scalaz expresses it in code:

  trait LensLaw {
    def identity(a: A)(implicit A: Equal[A], ev: F[Store[B, A]] =:= Id[Store[B, A]]): Boolean = {
      val c = run(a)
      A.equal(c.put(c.pos), a)
    }
    def retention(a: A, b: B)(implicit B: Equal[B], ev: F[Store[B, A]] =:= Id[Store[B, A]]): Boolean =
      B.equal(run(run(a) put b).pos, b)
    def doubleSet(a: A, b1: B, b2: B)(implicit A: Equal[A], ev: F[Store[B, A]] =:= Id[Store[B, A]]) = {
      val r = run(a)
      A.equal(run(r put b1) put b2, r put b2)
    }
  }

By making arbitrary turtles we can check if our turtleX is ok. We'll skip it, but make sure you don't define weird lens that break the law.

Links

There's an article by Jordan West titled An Introduction to Lenses in Scalaz, which I kind of skimmed and looks like Scalaz 6.

There's a video by Edward Kmett's Lenses: A Functional Imperative presented at the Boston Area Scala Enthusiasts (BASE).

Finally, there's a compiler plugin by Gerolf Seitz that generates lenses: gseitz/Lensed. The project seems to be at experimental stage, but it does show the potential of macro or compiler generating lenses instead of hand-coding them.

We'll pick it up from here later.