Chapter 12: Traversing the Stone

    And now for our next trick, we’ll look at traversals. We’ll watch types soar over one another as if they were trapeze artists holding our value intact. We’ll reorder effects like the trolleys in a tilt-a-whirl. When our containers get intertwined like the limbs of a contortionist, we can use this interface to straighten things out. We’ll witness different effects with different orderings. Fetch me my pantaloons and slide whistle, let’s get started.

    Let’s get weird:

    Here we read a bunch of files and end up with a useless array of tasks. How might we fork each one of these? It would be most agreeable if we could switch the types around to have instead of [Task Error String]. That way, we’d have one future value holding all the results, which is much more amenable to our async needs than several future values arriving at their leisure.

    Here’s one last example of a sticky situation:

    1. // getAttribute :: String -> Node -> Maybe String
    2. // $ :: Selector -> IO Node
    3. // getControlNode :: IO (Maybe (IO Node))
    4. const getControlNode = compose(map(map($)), map(getAttribute('aria-controls')), $);

    Look at those IOs longing to be together. It’d be just lovely to join them, let them dance cheek to cheek, but alas a Maybe stands between them like a chaperone at prom. Our best move here would be to shift their positions next to one another, that way each type can be together at last and our signature can be simplified to IO (Maybe Node).

    Type Feng Shui

    The Traversable interface consists of two glorious functions: sequence and traverse.

    Let’s rearrange our types using sequence:

    1. sequence(List.of, Maybe.of(['the facts'])); // [Just('the facts')]
    2. sequence(Task.of, new Map({ a: Task.of(1), b: Task.of(2) })); // Task(Map({ a: 1, b: 2 }))
    3. sequence(IO.of, Either.of(IO.of('buckle my shoe'))); // IO(Right('buckle my shoe'))
    4. sequence(Either.of, [Either.of('wing')]); // Right(['wing'])
    5. sequence(Task.of, left('wing')); // Task(Left('wing'))

    See what has happened here? Our nested type gets turned inside out like a pair of leather trousers on a humid summer night. The inner functor is shifted to the outside and vice versa. It should be known that sequence is bit particular about its arguments. It looks like this:

    1. // sequence :: (Traversable t, Applicative f) => (a -> f a) -> t (f a) -> f (t a)
    2. const sequence = curry((of, x) => x.sequence(of));

    Let’s start with the second argument. It must be a Traversable holding an Applicative, which sounds quite restrictive, but just so happens to be the case more often than not. It is the t (f a) which gets turned into a f (t a). Isn’t that expressive? It’s clear as day the two types do-si-do around each other. That first argument there is merely a crutch and only necessary in an untyped language. It is a type constructor (our of) provided so that we can invert map-reluctant types like Left - more on that in a minute.

    Using sequence, we can shift types around with the precision of a sidewalk thimblerigger. But how does it work? Let’s look at how a type, say Either, would implement it:

    1. class Right extends Either {
    2. // ...
    3. sequence(of) {
    4. return this.$value.map(Either.of);
    5. }
    6. }

    Ah yes, if our $value is a functor (it must be an applicative, in fact), we can simply map our constructor to leap frog the type.

    You may have noticed that we’ve ignored the of entirely. It is passed in for the occasion where mapping is futile, as is the case with Left:

    1. class Left extends Either {
    2. // ...
    3. return of(this);
    4. }
    5. }

    We’d like the types to always end up in the same arrangement, therefore it is necessary for types like Left who don’t actually hold our inner applicative to get a little help in doing so. The Applicative interface requires that we first have a Pointed Functor so we’ll always have a of to pass in. In a language with a type system, the outer type can be inferred from the signature and does not need to be explicitly given.

    Here we have two different functions based on if we map or traverse. The first, partition will give us an array of Lefts and Rights according to the predicate function. This is useful to keep precious data around for future use rather than filtering it out with the bathwater. validate instead will only move forward if everything is hunky dory. By choosing a different type order, we get different behavior.

    Waltz of the Types

    Time to revisit and clean our initial examples.

    1. // readFile :: FileName -> Task Error String
    2. // firstWords :: String -> String
    3. const firstWords = compose(join(' '), take(3), split(' '));
    4. // tldr :: FileName -> Task Error String
    5. const tldr = compose(map(firstWords), readFile);
    6. traverse(Task.of, tldr, ['file1', 'file2']);
    7. // Task(['hail the monarchy', 'smash the patriarchy']);

    Using traverse instead of map, we’ve successfully herded those unruly Tasks into a nice coordinated array of results. This is like Promise.all(), if you’re familiar, except it isn’t just a one-off, custom function, no, this works for any traversable type. These mathematical apis tend to capture most things we’d like to do in an interoperable, reusable way, rather than each library reinventing these functions for a single type.

    Let’s clean up the last example for closure (no, not that kind):

    1. // getAttribute :: String -> Node -> Maybe String
    2. // $ :: Selector -> IO Node
    3. // getControlNode :: IO (Maybe Node)
    4. const getControlNode = compose(chain(traverse(IO.of, $)), map(getAttribute('aria-controls')), $);

    Instead of map(map($)) we have chain(traverse(IO.of, $)) which inverts our types as it maps then flattens the two IOs via chain.

    Well now, before you get all judgemental and bang the backspace button like a gavel to retreat from the chapter, take a moment to recognize that these laws are useful code guarantees. ‘Tis my conjecture that the goal of most program architecture is an attempt to place useful restrictions on our code to narrow the possibilities, to guide us into the answers as designers and readers.

    An interface without laws is merely indirection. Like any other mathematical structure, we must expose properties for our own sanity. This has a similar effect as encapsulation since it protects the data, enabling us to swap out the interface with another law abiding citizen.

    Come along now, we’ve got some laws to suss out.

    1. const identity1 = compose(sequence(Identity.of), map(Identity.of));
    2. const identity2 = Identity.of;
    3. // test it out with Right
    4. identity1(Either.of('stuff'));
    5. // Identity(Right('stuff'))
    6. identity2(Either.of('stuff'));
    7. // Identity(Right('stuff'))

    This should be straightforward. If we place an Identity in our functor, then turn it inside out with sequence that’s the same as just placing it on the outside to begin with. We chose Right as our guinea pig as it is easy to try the law and inspect. An arbitrary functor there is normal, however, the use of a concrete functor here, namely in the law itself might raise some eyebrows. Remember a category is defined by morphisms between its objects that have associative composition and identity. When dealing with the category of functors, natural transformations are the morphisms and Identity is, well identity. The Identity functor is as fundamental in demonstrating laws as our compose function. In fact, we should give up the ghost and follow suit with our type:

    1. const comp1 = compose(sequence(Compose.of), map(Compose.of));
    2. const comp2 = (Fof, Gof) => compose(Compose.of, map(sequence(Gof)), sequence(Fof));
    3. // Test it out with some types we have lying around
    4. comp1(Identity(Right([true])));
    5. // Compose(Right([Identity(true)]))
    6. comp2(Either.of, Array)(Identity(Right([true])));
    7. // Compose(Right([Identity(true)]))

    This law preserves composition as one would expect: if we swap compositions of functors, we shouldn’t see any surprises since the composition is a functor itself. We arbitrarily chose true, Right, Identity, and Array to test it out. Libraries like quickcheck or can help us test the law by fuzz testing the inputs.

    As a natural consequence of the above law, we get the ability to fuse traversals, which is nice from a performance standpoint.

    1. const natLaw1 = (of, nt) => compose(nt, sequence(of));
    2. const natLaw2 = (of, nt) => compose(sequence(of), map(nt));
    3. // test with a random natural transformation and our friendly Identity/Right functors.
    4. // maybeToEither :: Maybe a -> Either () a
    5. const maybeToEither = x => (x.$value ? new Right(x.$value) : new Left());
    6. natLaw1(Maybe.of, maybeToEither)(Identity.of(Maybe.of('barlow one')));
    7. // Right(Identity('barlow one'))
    8. natLaw2(Either.of, maybeToEither)(Identity.of(Maybe.of('barlow one')));
    9. // Right(Identity('barlow one'))

    This is similar to our identity law. If we first swing the types around then run a natural transformation on the outside, that should equal mapping a natural transformation, then flipping the types.

    A natural consequence of this law is:

    In Summary

    Traversable is a powerful interface that gives us the ability to rearrange our types with the ease of a telekinetic interior decorator. We can achieve different effects with different orders as well as iron out those nasty type wrinkles that keep us from joining them down. Next, we’ll take a bit of a detour to see one of the most powerful interfaces of functional programming and perhaps even algebra itself: Monoids bring it all together

    Considering the following elements:

    1. // httpGet :: Route -> Task Error JSON
    2. // routes :: Map Route Route
    3. const routes = new Map({ '/': '/', '/about': '/about' });

    {% exercise %}
    Use the traversable interface to change the type signature of getJsons to
    Map Route Route → Task Error (Map Route JSON)

    {% initial src=”./exercises/ch12/exercise_a.js#L11;” %}

    1. // getRoutes :: Map Route Route -> Map Route (Task Error JSON)
    2. const getJsons = map(httpGet);

    {% solution src=”./exercises/ch12/solution_a.js” %}
    {% validation src=”./exercises/ch12/validation_a.js” %}
    {% context src=”./exercises/support.js” %}
    {% endexercise %}


    We now define the following validation function:

    1. // validate :: Player -> Either String Player
    2. const validate = player => (player.name ? Either.of(player) : left('must have name'));

    {% exercise %}
    Using traversable, and the validate function, update startGame (and its signature)
    to only start the game if all players are valid

    {% initial src=”./exercises/ch12/exercise_b.js#L7;” %}

    1. // startGame :: [Player] -> [Either Error String]
    2. const startGame = compose(map(always('game started!')), map(validate));

    {% solution src=”./exercises/ch12/solution_b.js” %}
    {% validation src=”./exercises/ch12/validation_b.js” %}
    {% context src=”./exercises/support.js” %}
    {% endexercise %}


    Finally, we consider some file-system helpers:

    {% exercise %}
    Use traversable to rearrange and flatten the nested Tasks & Maybe

    {% initial src=”./exercises/ch12/exercise_c.js#L8;” %}

    {% solution src=”./exercises/ch12/solution_c.js” %}
    {% validation src=”./exercises/ch12/validation_c.js” %}
    {% context src=”./exercises/support.js” %}
    {% endexercise %}