Functions as Functors

Hello! I was explaining the other day how Function’s implementations of the different typeclasses can be useful, and I thought I might as well write them up in case they can be useful to someone. It’s also much easier than writing 140-character blocks. Specifically, we’ll go through Functor, Apply, and Chain, with examples all the way.

In all these cases, I’ve represented the Function type as ((->) x). So, Function a means ((->) x) a, which probably looks more familiar as x -> a. Read it as, “A function from values of type x to values of type a”. I’ve written the signatures in all three styles, along with the general rule where applicable.

Functor

//       f  a ~> (a -> b) ->       f  b
// Function a ~> (a -> b) -> Function b
// ((->) x) a ~> (a -> b) -> ((->) x) b
//  (x -> a)  ~> (a -> b) ->  (x -> b)
Function.prototype.map = function (that) {
  return x => that(this(x))
}

What map does is create a new function that calls the original, and then the mapping function, to build up a kind of pipeline:

const myFunction =
  (x => x.length)
  .map(x => x + 2)
  .map(x => x / 2)

// Returns "7.5"
myFunction('Hello, world!')

Of course, you probably know of another tool for creating pipelines of functions:

// The `map` we know and love.
// map :: Functor f => (b -> c)
//                  -> f b -> f c
const map = f => xs => xs.map(f)

// WHAT IF I TOLD YOU...

// Read `f` in the above as ((->) a)...
// compose :: (b -> c)
//         -> (a -> b)
//         ->  a -> c
const compose = map // WHAT

I know, right?

Apply

// f  a ~> f (a -> b)
//      -> f  b

// Function a ~> Function (a -> b)
//            -> Function b

// ((->) x) a ~> ((->) x) (a -> b)
//            -> ((->) x) b

//  (x -> a) ~> (x -> a -> b)
//           ->  (x -> b)

Function.prototype.ap = function (that) {
  return x => that(x)(this(x))
}

Some of you will know that I find Apply to be best appreciated through the lens of lift2. As mentioned in the Apply article of the Fantasy Land series, lift2 combines two contexts into one:

// (a -> b -> c) -> f a
//               -> f b
//               -> f c

// (a -> b -> c) -> Function a
//               -> Function b
//               -> Function c

// (a -> b -> c) -> ((->) x) a
//               -> ((->) x) b
//               -> ((->) x) c

// (a -> b -> c) -> (x -> a)
//               -> (x -> b)
//               -> (x -> c)

const lift2 = (f, a, b) => b.ap(a.map(f))

What we end up with is a function of one argument (type x), which applies to f the result of applying that argument to a and b. Ramda fans may recognise this as converge:

// From the Ramda docs:
const divide = x  => y => x / y

const sum = xs => xs.reduce(
  (x, y) => x + y, 0
)

const length = xs => xs.length

// divide(sum, length)
//   === 28 / 7 === 4
const average = lift2(divide, sum, length)
average([1, 2, 3, 4, 5, 6, 7])

// This generalises to liftN!
const lift3 = (f, a, b, c) =>
  c.ap(b.ap(a.map(f)))

// Some password checks...

const longEnough =
  length.map(x => x > 8) // Functor!

const hasNumber = x =>
  null !== x.match(/\d+/g)

const hasUppercase = x =>
  null !== x.match(/[A-Z]+/g)

// Some combining function...
const and3 = x => y => z => x && y && z

// Combine the three functions with and3
const passwordCheck = lift3(
  and3, longEnough
      , hasNumber
      , hasUppercase)

passwordCheck('abcdef')    // false
passwordCheck('abcdefghi') // false
passwordCheck('abcdefgh1') // false
passwordCheck('Abcdefgh1') // true

So, we can take a set of functions that act on the same type, and combine their outputs with another function. This is the magic of ap.

Chain

// m  a ~> (a -> m b)
//      -> m b

// Function a ~> (a -> Function b)
//            -> Function b

// ((->) x) a ~> (a -> ((->) x) b)
//            -> ((->) x) b

//  (x -> a) ~> (a -> x -> b)
//           -> x -> b
Function.prototype.chain =
  function (that) {
    return x => that(this(x))(x)
  }

The Function type also has a Chain implementation, with an eventual type that looks really similar to Functor. The difference this time, however, is that the intermediate function can access the original argument. No matter how many functions we chain together, the second argument to the chain function will always be of type x, and the value of the original input.

const myNextFunction =
  (x => x.length)
  .map(x => x + 2)
  .map(x => x / 2)
  .chain(x => s =>
    'started from the ' + s
      + ' now we ' + x)

// started from the Hello! now we 4
myNextFunction('Hello!')

So, after two map calls that transform the input entirely, chain re-introduces the original value. In fact, we can call chain whenever we want access to that initial value. We can’t modify the value - it’s effectively read-only. This is why the Function type, when used as a Functor, is often called Reader.

Reader’s most obvious application is to provide a way of accessing some global application state (e.g. config or a database):

// a ->       f  a
// a -> Function a
// a -> ((->) x) a
// a ->   x -> a
Function.of = x => _ => x

const MyApp =
  // Set a "starting value"... or just
  // a function that _takes_ a starting
  // value and THEN the environment!
  Function.of('Hello, ')

  // Get a variable from environment,
  // augment the string.
  .chain(x => ({ isProduction }) =>
    x + (isProduction ? 'customers'
                      : 'developers'))

  // Augment the string.
  .map(x => x + '!')

  // Get ANOTHER variable from the
  // environment, augment the string.
  .chain(x => ({ caller }) =>
    caller === 'browser'
    ? '<h1>' + x + '</h1>'
    : '{ data: "' + x + '}" }')

  // Send to the consumer.
  .map(sendToConsumer)

// This object becomes our app's "global
// config", and the function returns
// whatever `sendToConsumer` would if given
// '{ data: "hello, customers!" }'
MyApp({
  isProduction: true,
  caller: 'api'
})

Here, our argument is a config object, and our app is free to pick bits out whenever it chooses. Also notice the Function.of at the top - this is the Pointed implementation. This was the ingredient missing from Apply for an Applicative, and from Chain for a Monad. Developers, meet the Reader monad, in all its glory.

I think Function provides three instances here that are of immediate benefit, and they all have uses in practical code! I hope you found this useful, and I hope to see a lot more uses of Function as a Functor in the wild.

Until Monday, have fun, enjoy the long weekend, and take care! ♥