Tuesday 5 June 2012

Scala: Lenses 2

Looks like Lenses topic should not be closed without discussing compose method. This method decreases the count of lenses we have to produce. We called it M * N size issue in previous session.
This time it's hard to not jump into function programming math mess, but we will try.

Function1

Indeed you've heard on Function1: class that implements special case for Function. Function of course makes functions are living as first class citizens in Scala. Let's look inside (removing unimportant parts):


    trait Function1[ -T1, +R] extends AnyRef { self =>

        def apply(v1: T1): R
        def compose[A](g: A => T1): A => R = { x => apply(g(x)) }
        ...
    }

Apply method knows how from T1 make R
Lets speak about compose in detail: there a function that knows how A can become T1. While T1 is an input into apply, we can compose A => T1 and T1 => R into A => R. Easy isn't it? We just putting a result from one function as a parameter into apply.

Compose for Lenses

It's time to review our complex:) construction again:

    case class Weapon(name: String, ammo: Long)
    case class Soldier(rank: Int, weapon: Weapon) 

Remember how to implement lens for changing ammo in weapon?

    val ammoL = Lens[Weapon, Long](...)

and then changing a weapon of soldier

    val weaponL = Lens[Soldier, Weapon](...)

both operates with get method, from weaponL to ammoL:

    Function1 [Soldier, Weapon] and Function1 [Weapon, Long]

If we will execute compose :  Function1 [WeaponLong] compose Function1 [SoldierWeapon] it will return Function1 [Soldier, Long].

Not bad for get function? Coder's intuition should push us to insurance that set can be implemented as well via already defined Lenses. I'm sure U already tired here, just showing a final solution:

    case class Lens[R, F](get: R => F, set: (R, F) => R) {
        def compose[Q](lens2: Lens[Q, R]): Lens[Q, F] =
               Lens (get = get compose lens2.get,
                         set = (q, f) => lens2 set(q, set(lens2 get q, f)))
    }

Putting it together and changing soldiers ammo to 0:

    val weaponL = Lens[Soldier, Weapon](
        get = _.weapon,
        set = (s, w) => s.copy(weapon = w)
    )

    val ammoL = Lens[Weapon, Long](
        get = _.ammo,
        set = (w, a) => w.copy(ammo = a)
    )

    val soldierAmmoL = ammoL compose weaponL

    soldierAmmoL.set(soldier, 0)

We got Lens, from existed, without implementing it explicitly! Victory!

No comments:

Post a Comment