Skip to content

Instantly share code, notes, and snippets.

@wayneseymour
Forked from JamieDixon/list-applicative.md
Created November 13, 2020 21:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wayneseymour/8bc0fefed2764df4236555eca3f4206e to your computer and use it in GitHub Desktop.
Save wayneseymour/8bc0fefed2764df4236555eca3f4206e to your computer and use it in GitHub Desktop.

List comprehensions

To see a working version of the code check out https://codesandbox.io/s/j497w26383 and open the console.

This file demonstrates a very basic List as an applicative functor for the purpose of demonstrating a simple list comprehension.

The idea for this was taken from: https://egghead.io/lessons/javascript-list-comprehensions-with-applicative-functors by https://twitter.com/drboolean

Here I'm implementing a simple List in order to try out the idea behind list comprehensions as outlined in the video.

This is not a production-use data structure. It's just me playing around to understand the internals better so that I feel comfortable using list comprehensions with a more sturdy data structure such as those provided in: https://github.com/DrBoolean/immutable-ext

What do we want?

We want to combine 3 lists such that for each entry on the left, it is combined with every entry on the right. For example, given two lists: List(['teeshirt', 'sweater']) and List(['small', 'medium', 'large']) we want to produce a new list of: List(['teeshirt-small', 'teeshirt-medium', 'teeshirt-large', 'sweater-small', ...])

The traditional approach to this would be to use nested for-loops. Each time we have another List on the right, we would create a new inner loop. Let's see how this is achieved using list comprehensions.

A basic List implementation

// A little helper. We'll use this down at the bottom.
const liftA3 = (f, fx, fy, fz) =>
  fx
    .map(f)
    .ap(fy)
    .ap(fz);

// The List Applicative Functor
function List(x) {
  this.x = x;
}

List.prototype.map = function(f) {
  return new List(Array.isArray(this.x) ? this.x.map(f) : f(this.x));
};

// Flatten an array of List by a depth of 1
List.prototype.flatten = function() {
  return Array.isArray(this.x)
    ? List.of(this.x.reduce((agg, next) => [...agg, ...next.x], []))
    : this.x;
};

List.prototype.ap = function(other) {
  return this.map(f => other.map(f)).flatten();
};

// Little helper so we can see what's inside our List
List.prototype.inspect = function() {
  return `List([${this.x}])`;
};

List.of = x => new List(x);

Now let's use our new List!

 List.of(x => y => z => `${x}-${y}-${z}`)
 .ap(List.of(['teeshirt', 'sweater']))
 .ap(List.of(['large', 'medium', 'small']))
 .ap(List.of(['green', 'red', 'blue', 'pink']))
 .inspect();

 // or a simpler looking version using our liftA3

liftA3(
  x => y => z => `${x}-${y}-${z}`,
  List.of(["teeshirt", "sweater"]),
  List.of(["large", "medium", "small"]),
  List.of(["green", "red", "blue", "pink"])
).inspect();

To see a working version of the code check out https://codesandbox.io/s/j497w26383 and open the console.

The output of both our inspect() calls is:

List([
teeshirt-large-green,
teeshirt-large-red,
teeshirt-large-blue,
teeshirt-large-pink,
teeshirt-medium-green,
teeshirt-medium-red,
teeshirt-medium-blue,
teeshirt-medium-pink,
teeshirt-small-green,
teeshirt-small-red,
teeshirt-small-blue,
teeshirt-small-pink,
sweater-large-green,
sweater-large-red,
sweater-large-blue,
sweater-large-pink,
sweater-medium-green,
sweater-medium-red,
sweater-medium-blue,
sweater-medium-pink,
sweater-small-green,
sweater-small-red,
sweater-small-blue,
sweater-small-pink
])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment