Part 3 of the series. Refer to the Part 1 to understand the basics and Part2 for Isomorphic lenses.

Last time we talked about Isomorphic lenses, how to create them, use them and I described some of the practical applications. Today I'll introduce you to the concept of Traversable lenses. First I'll explain the theoretical concepts and laws of Traversables and then I'll show you some real world examples. I will also give you the ready made tools so that you can start using the Traversable lenses right away after you've finished reading this article.

Theory

JavaScript has a specification for interoperability of common algebraic structures. It's called Fantasy Land Specification. One of the algebraic structure it describes is Traversable. A value that implements the Traversable specification must also implement the Functor and Foldable specifications. It must also satisfy the laws of naturality, identity and composition. I won't go into the details of proofs for these laws, but if you're interested you can find them here.

So what is a Traversalbe ? Tom Harding already did a great job explaining the Traversables. Tom is using JavaScript code examples to translate the Fantasy Land Traversable specification into something developers can relate to. I'll follow up on Toms ideas and use the same code examples tactics to expain the concept. Just for the reference: I am using Ramda (represented by R) and Ramda-Adjunct (represented by RA) in my code examples to compose behaviors. So please refer to their respective documentations to understand what the functions that I'm using do.

Traversable is any value that has a traverse method with the following signature:

traverse :: Applicative f, Traversable t => t a ~> (TypeRep f, a -> f b) -> f (t b)

Yeah I know this looks completely crazy. Let's translate this into code. We all now Array in JavaScript so let's turn an Array into the Traversable by adding a traverse method to Array.prototype with the signature described above.

Array.prototype.traverse = function (T, f) { return this .reduce( (acc, x) => R.lift(R.append)(f(x), acc), T. of ([]) ); };

T - is a Type representative (TypeRep). Imagine a TypeRep to be class with a static method of . When of is called with the argument, it returns the instance of the TypeRep with the internal state set to the argument it received.

f - is a function that returns a value of the type represented by T

Now we have the Array which is a Traversable. Let's use it to demonstrate what it can do and call the traverse method on Array.

const add1 = x => RA.Identity.of(x + 1 ); [ 1 , 2 ].traverse(RA.Identity, add1);

Yeah I know, this looks crazy again. We are Mapping an Applicative-returning function ( add1 ) over a Traversable ( Array ), then we're transforming the resulting Traversable of Applicative into an Applicative of Traversable. Whaaat ? Well, one code example is worth 1000 words so let me define these two terms in code examples to clarify.

Traversable of Applicative

[RA.Identity.of( 2 ), RA.Identity.of( 3 )]

Applicative of Traversable

RA.Identity.of([2, 3])

Traversable lenses

Now that we got our theory right, let's talk about how to generalize the Traversal into the lens concept. Ramda contains a function called traverse that allows us to generalize the Traversal concept. So instead of modifying the Array.prorotype and manually adding traverse method we can use R.traverse to achieve exactly the same result.

const add1 = x => RA.Identity.of(x + 1 ); R.traverse(RA.Identity.of, add1, [ 1 , 2 ]);

Notice that instead of TypeRep the traverse function consumes directly the of function. This is due to the fact that R.traverse implements Fantasy Land Specification v1.y.z. This means that it will also accept Array or any Fantasy Land Specification v1.y.z compatible Traversable. There is already a new version of Fantasy Land Specification v3.y.z. and I will demonstrate what is difference between v1.y.z and v3.y.z again on code example using our Array usecase.

Array .prototype.traverse = function ( T, f ) { return this .reduce( (acc, x) => R.lift(R.append)(f(x), acc), T.of([]) ); }; Array .prototype.traverse = function ( f, of ) { return this .reduce( (acc, x) => R.lift(R.append)(f(x), acc), of ([]) ); };

Now that we have generalization in form of R.traverse, let's lift this generalization into lens context. At this point I will just tell you that we have already implemented this lens generalization in Ramda Adjunct. Dealing with how we did it will not be of any additional value to this article. If you're really interested you can inspect this Pull Request for actual implementation. The important thing is that we managed to do it and we created RA.lensTraverse lens creator that is using R.traverse under the hood. It has only one required argument, the of function from TypeRep (TypeRep.of).

Now let's use RA.lensTraverse to rewrite our usecase using lenses to demonstrate it's power and flexibility.

Going over the Traverse lens

const add1 = x => RA.Identity.of(x + 1 ); R.over(RA.lensTraverse(RA.Identity.of), add1, [ 2 , 3 ]);

Viewing through the Traverse lens

R.view(RA.lensTraverse(RA.Identity. of ), [ RA.Identity. of ( 2 ), RA.Identity. of ( 3 ), ]); // => RA.Identity([ 2 , 3 ])

Setting through the Traverse lens

R.set(RA.lensTraverse(RA.Identity. of ), RA.Identity. of ( 2 ), [ RA.Identity. of ( 2 ), RA.Identity. of ( 3 ), ]); // => RA.Identity([ 2 , 2 ])

RA.lensTraverse is part of Ramda Adjunct 2.7.0 release. Feel free to experiment with it to fully understand how concept of Traversals can be generalized by a simple lens.

Real world applications

So now you're thinking: How does this nonsense apply to the development of real world applications ? Well, if you're fun of Functional Monadic Programming (as I am), you maybe came to the conclusion that Promises are broken due to the fact that they are eager, not cancel-able, they compose via pseudo Chain interface and also due to the fact how they handle errors (mixing exception with expected failures). Because of these facts I'm usually writing Promise returning functions as low level application API and then using abstractions like Fluture or Task to abstract the way I work with these low level functions. Well at least I've been doing that until I discovered principles of reactive programming and Observables in RxJS. Both Observables and Promises are Monads and so is the Fluture.

Lets stop for a while with Fluture - FantasyLand compliant (monadic) alternative to Promises. Using Flutures in you higher level asynchronous application code can turn you code into really nice and declarative soup. As I already mentioned, Fluture is a FantasyLand compliant algebraic structure. But it is also an Applicative. That means we can apply our RA.lensTraverse lens.

const Future = require ( 'fluture' ); const { over, map, add } = require ( 'ramda' ); const { lensTraverse } = require ( 'ramda-adjunct' ); const promiseFn = (delay) => new Promise (resolve => { setTimeout(resolve.bind( null , delay), delay * 1000 ); }); const async1 = Future.encaseP(promiseFn, 1 ); const async2 = Future.encaseP(promiseFn, 2 ); const async3 = Future.encaseP(promiseFn, 3 ); const async4 = Future.encaseP(promiseFn, 4 ); const async5 = Future.encaseP(promiseFn, 5 ); const traversableOfApplicative = [async1, async2, async3, async4, async5]; const applicativeOfTraversable = over(lensTraverse(Future.of), map(add( 1 )), traversableOfApplicative); applicativeOfTraversable.fork( console .error, console .dir);

Yes! With a couple of lines of code we did real magic here. We created five lazy asynchronous tasks (Applicative), locked them in an Array (Traversable) and mapped a function that adds number one to the result of all these asynchronous tasks. The point is that we did all that before we even run our code with final statement of our program applicativeOfTraversable.fork(console.error, console.dir). Hence that is the difference between the eagerness of the Promises and laziness of the Flutures.

RA.lensTraverse will be available in next minor release of Ramda-Adjunct 2.7.0. We plan the release on 24.4.2018. Along with that our team prepared couple of other new handy utils for you, but let's keep that as a surprise.

Conclusion

In the next and last article of this series we will look into the world of Folds or Foldable lenses. After we're finished with the Folds, you should be entitled to call your self a lens Ninja.

Like always I end my article with the usual axiom: Define your code-base as pure functions and lift them only if and when needed. In this case into the lens context. And compose, compose, compose...

Functional Lenses in JavaScript series: