Traversable Functors
We already have studied four of the five algebras that are typically used for data structure manipulation:
- Monoid --
Fold -- Linq's Aggregate
- Functor --
Map -- Linq's Select
- Applicative Functor --
Apply -- Linq's does not include this.
- Monad --
FlatMap -- Linq's SelectMany
The fifth one is
Traversable. To traverse means to walk across, and that is exactly what Traversable generalises: walking across a structure, collecting results at each stop.
Traverse resembles the mapping functions we have seen in other data types, however rather than using its function argument to insert functorial contexts under the original structure; as might be done with
Map /
Select, or to modify the structure as
FlatMap /
SelectMany does.
Traverse essentially adds an extra layer of context
on the top of the structure, which allows for
effectful traversals − traversals which produce an overall effect (i.e. a new outer layer of context).
Data types encapsulating Data types
Traversable is used more typically adjacent to Applicative Functor and Monadic processes, where the transitory state of variable is a Monadic Data type that encapsulates another Monadic data type, for example; let's assume we are using the Maybe type as our monadic composition type to hold a state that is either a valid value (
Just) or null (
Nothing). Now imagine one of the monadic functions returning a List of something; a database query response, or a deserialised web API -- our end result would be a
Maybe encapsulating a List; a data type encapsulating another data type.
Code Example:
C#:
var r = Just(List(1, 2, 3, 4, 5, 6));
// Maybe<List<int>>[List<int>[ 1, 2, 3, 4, 5, 6 ]];
To access and / or transform the values is a bit awkward, because we need to first evaluate the outer
Maybe before we can iterate over the integers contained in the
List.
This is a scenario where
Traverse can make things a little easier by inverting the data types.
C#:
var r = Just(List(1, 2, 3, 4, 5, 6));
// Maybe<List<int>>[List<int>[ 1, 2, 3, 4, 5, 6 ]];
var traversedR = r.Traverse(x => x);
// List<<Maybe<int>>[Maybe<int>[1], Maybe<int>[2], Maybe<int>[3], Maybe<int>[4], Maybe<int>[5], Maybe<int>[6]]
Now we can easily iterate over traverseR; the x => x computation is btw the
Id function, meaning we can replace that with a point-free version, e.g.
C#:
var traversedR = r.Traverse(Id);
// List<<Maybe<int>>[Maybe<int>[1], Maybe<int>[2], Maybe<int>[3], Maybe<int>[4], Maybe<int>[5], Maybe<int>[6]]
Considering that the need to invert the outer and inner data types is a frequently used operation; the FP implementation will typically implement this as a separate operation called
Sequence, which is the same as executing
Traverse with the
Id function, for example:
C#:
var traversedR = r.Traverse(Id);
var sequenceR = r.Sequence();
traverseR and
sequenceR results are isomorphic.
Traverse is not limited to just inversion
As mentioned previously
Traverse is a functor; or more specifically a
functor that not only facilitates inversion of inner and outer data type, it also facilitates
effectful traversals with it's higher order function parameter, for example we can not only invert the data types, we can also choose to, as example, filter out the even integers:
C#:
var r = Just(List(1, 2, 3, 4, 5, 6));
var evenTraversedR = r.Traverse(x => x.FlatMap(y => (y % 2 == 0) ? List(y) : List<int>())).ToList();
// List<<Maybe<int>>[Maybe<int>[2], Maybe<int>[4], Maybe<int>[6]]
... and that's it... like our
Map /
Select method
Traverse is a versatile higher order function for transformation of values contained in a datatype; with the added advantage of also inverting the inner and outer data types. We can of course also encapsulate our return type in another data type layer by using
Map instead of
FlatMap if so needed.
Next post; we'll review how this algebra has been implemented on our various FP data types.