Skip to content

Instantly share code, notes, and snippets.

@LoganBarnett
Created August 2, 2020 05:22
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 LoganBarnett/814ea9fb715632a364b69d725d367c60 to your computer and use it in GitHub Desktop.
Save LoganBarnett/814ea9fb715632a364b69d725d367c60 to your computer and use it in GitHub Desktop.
/**
* Typically when we think of map we think of lists. Let's take the ML notation
* for map of a list:
*
* ((a -> b) -> List a -> List b)
*
* With ML notation, the best way to read this is that the last arrow is the
* return type. The reason the notation exists this way is because functions in
* a functional language can be modeled as unary, or meaning they only have one
* argument. One can imagine multiple argument functions as sytactic sugar.
*
* Take this add example:
*/
const add = x => y => x + y
/**
* The ML notation for add is (number -> number -> number), where the first two
* numbers are paramters and the third is the return type.
*
* Embedded functions are delieniated with parenthesis. Looking back at our map
* example:
*
* ((a -> b) -> List a -> List b)
*
* (a -> b) means the first argument is a function that takes an "a" and returns
* a "b". In ML's type notation, type parameters can appear as lowercase
* letters. It's also notable that type parameters such as a and b can be the
* same type, but they are permitted to differ. Depending on how deep you get
* into type theory, a and b could represent any different form of data. For
* example the type of a could the number 5 (not just any number), and type b
* would be something related to 5 by virtue of the transformation function.
*
* In functional programming, usually the arguments that "configure" or
* specialize the function come first. The data portion of the function comes
* last. If you come from a background such as Ruby, C#, Java, or JavaScript
* (think lodash), this will seem backwards to you. The reason this is done is
* to aid with partial application - an important tool in function composition.
*
* We could write our list's map function like this:
*/
const mapList = f => xs => xs.map(f)
/**
* Yes, we're borrowing from the existing Array.prototype.map here. We will come
* back to this function later.
*
* Back to map: What if we took a step back from our map signature. Should we
* care about whether or not it's a list we want to transform? Could we apply
* map in other contexts? Some languages allow map to operate on a map
* structure, where each value undergoes a transformation and the operation
* produces a new map structure.
*
* In category theory, we can represent lists in a more generic sense: A
* functor. In the vaguest sense a functor is a kind of structure. Even calling
* a functor a "container" might make some intuitive sense, but that can be too
* constraining a term.
*
* We generally write functors as "F a", meaning a functor parameterized by type
* a. Functors themselves are type parameters, which are further paramterized by
* "a". This feature in a typed language is called Higher Kinded Types, or HKT.
* Without it, we must express these types individually. So that means one map
* type for lists, one for associative lists, and so on.
*
* With the functor, the ML expression for map becomes:
*
* ((a -> b) -> F a -> F b)
*
* Tiny! Now let's take a look at something more complicated in JS: Promises.
* Stay with me while we stitch this together. Promise in JS has a then method
* that can be used to operate on the promises' data. You could think of it as a
* callback that is executed when the promise resolves, but that's very
* imperative thinking and we don't do that here. For a moment, let's make a
* functional-esque version of Promise's then:
*/
const then = f => p => p.then(f)
/**
* This looks familiar, but the names are different. This is essentially the
* same thing as map. Let's do some renaming and create a real method for
* Promise: map
*/
Promise.prototype.map = function(fn) { return this.then(fn) }
/**
* So basically we made an alias here. There's no material difference between
* map and then. The map method just delegates to then. Let's go back to our
* list version of map and rewrite it to account for functors:
*/
const map = fn => f => f.map(fn)
/**
* Now map works for both Array and Promise. One of the things we get from doing
* this is we create an ecosystem that begs for function composition. With
* function composition we're just stitching together functionality from things
* we already understand because they are very simple.
*
* Even if we don't go deep into a rich ecosystem of curried functions, we can
* still benefit having these two things nudged a little closer. Take our add
* function we wrote earlier, and let's us it with map.
*/
[1, 2, 3].map(add(1)) // [2, 3, 4]
[1, 2, 3]
.map(add(2)) // [2, 3, 4]
.map(add(1)) // [3, 4, 5]
/**
* And then we do a similar thing with Promise.
*/
Promise.resolve(1)
.map(add(2)) // Resolves as 3.
.map(add(1)) // Resolves as 4.
/**
* Remember we didn't actually change anything meaningful in Promise. We just
* renamed a method, essentially. When we apply map, we keep the structure and
* operate on something the structure is responsible for. We can extend this to
* Maybe and Either as well.
*/
new Just(1) // Just is the "value" form of Maybe.
.map(add(1)) // Maybe(2)
.map(add(2)) // Maybe(4)
new Nothing() // Nothing is the "null" form of Maybe.
.map(add(1)) // Nothing
.map(add(2)) // Yep. Still Nothing.
new Right(1) // Right is the "value" form of Either, by convention.
.map(add(1)) // Either(2)
.map(add(2)) // Either(3)
new Left(1) // Left is the "left" form of Either.
.map(add(1)) // Either(1)
.map(add(2)) // Either(1)
/**
* I hope this has been informative of the power of map. When you hear FP
* enthusiasts talking about how most everything can be handled with some
* combination of map, filter, and fold (reduce), one can see how it's more than
* just list comprehensions which few of us get to remain in when writing real
* world software.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment