Lenses Under the Hood
We explore what makes lenses tick, and experiment with functors along the way. (12-15 min. read)
We'll cover the following...
Check out Ramda’s lens source code. As of this course, it looks like this
There’s a lot to unpack here but I think we can do it. Starting with the first line…
Currying
Look at the top of the source code file.
Ok, not too scary–they imported an internal _curry2 function. Why?
It’s currying lens. Ramda curries everything so _curry2 seems to be specialized for functions with two arguments.
Why a specialized curry()?
Ramda’s curry is dynamic, and handles all types of functions with differing argument counts.
If you know how many arguments you need, however, using a specialized curry can optimize your function’s run-time. You can ignore handling any other argument case, because you know your function expects 2, 3, 4, etc arguments.
Next line!
So lens returns a function after receiving its getter and setter. Let’s test that out.
Yep! This returned a function that takes one parameter, toFunctorFn. “To Functor Function”
Sounds like a function that turns something into a functor. We know from the previous section that functors are containers that can hold any value.
Using Functors
What’s the next step after giving lens a getter/setter? Pass it to view, set, or over.
That’s pretty cool. With the getter/setter a lens looks like this
After giving it to view, though…
…everything’s magically resolved and we get 'Bobo'.
View Magic
That means view satisfied all of lens’ requirements in a single shot, so it’s our next point of investigation. Take a look at its source code. There’s a variable, Const.
It creates a functor with two properties
valuefantasy-land/mapmethod
We’ve already seen this. It wants to override map's functionality.
Now look at view's implementation.
Const is the toFunctorFn here. It will tell lens how to turn something into a functor.
Let’s extract that step into our own playground to experiment a little. We just need to define our own Const and give it to the name lens we created earlier.
Yet Another Function
It returned another function, when will the madness end?!
This one needs a target. Let’s see how view satisfies this requirement.
view's second argument, x
gets forwarded to lens
Back to the lab!
Whoa whoa, look at that!
{
value: 'Bobo',
'fantasy-land/map': [Function: fantasyLandMap]
}
That’s our desired value, Bobo, after passing through Const. It’s now a functor!
So we somehow get back a functor(Bobo) after this code runs
After getting its target, lens begins mapping.
Why map()?
But mapping is to change a functor’s value. view's only supposed to get a value, not modify it!
map does change values, but not if you override it…
Let’s dissect the following code snippet.
What are the pieces?
Look closer at that functor’s fantasy-land/map method.
Overridden to Do Nothing
It does nothing but return itself.
This makes sense because view's job is to return the data untouched. By returning this it effectively ignores map.
To contrast, look at over's toFunctorFn in the source code.
It’s an Identity functor, similar to the previous lesson!
Instead of doing nothing like view, map's being told to transform the lens value according to the supplied function.
So whether you’re reading with view or writing with set/over, your toFunctorFn is the key to making it all work with map.
Summary
-
Lenses use functors, therefore implement everything, even reading a value, with
map. -
This is possible because an object can define its own
fantasy-land/mapmethod to take full control of the procedure. -
In
view's case,fantasy-land/mapjust returns itself (the requested data). -
In
over's case,fantasy-land/maptransforms the data with a given function.
We’re exploring how lenses compose next. That’s right, we’re going to start combining them to find shocking results!