Applicative Functor

In notebook:
FrontEndMasters Hardcore Functional
Created at:
2017-07-16
Updated:
2017-07-16
Tags:
Functional Programming JavaScript Fundamentals

video on Vimeo

We've arleady seen pointed functors, this is another kind.

“Run full computations in a context”

functions: ap, liftA2, liftA..n


We get a partially applied Container

  map(add(1), Container(2))
//=> Container(3)

map(add, Container(2))
// => Container(add(2))

This is not ideal. Let's try to solve this with our current toolset.


Code example:

  var add = _curry(function (x,y) {return x + y})

var c2 = Container(2)
var c3 = Container(2)

var c_add2 = map(add, c2) // Container(add(2))

var res = map(function (add2) {
  return map(add2, c3)
}, c_add2)

console.log(res) // => Container(Container(5))

Let's continue with flatmap

  var add = _curry(function (x,y) {return x + y})

var c2 = Container(2)
var c3 = Container(2)

var c_add2 = map(add, c2) // Container(add(2))

var res = flatMap(function (add2) {
  return map(add2, c3)
}, c_add2)

console.log(res) // => Container(5)

This is better, but still a bit awkward. Could be cleaner.

#Applicatives

ap :: A (a -> b) -> A a -> A b


Pointed Functor + ap = Applicative
aka: <*>

Reviews Pointed Functors: functors that have an of method:

of :: a -> F a

Usage examples of pointer functions:

  Container.of(split)       Future.of(match(/dubstep/))
// Containe(split)        // Future(match(/dubstep/))

Maybe.of(reverse)         EventStream.of(replace(/dubstep/, 'shoegaze'))
// Maybe(reverse)         // EventStream(match(/dubstep/, 'shoegaze'))

Explains that the of pattern is even better with Futures, as they expect a callback function.


##Applicatives

We can apply a Container of a function on a Container of a value:

  Container.of(f).ap(Container(x))
//=> Container(f(x))

// It can even apply the container of a function on both containers:
Container.of(f).ap(Container(x)).ap(Container(y))
//=> Container(f(x, y))
// This chains the functors

A more concrete example:

  Container.of(add).ap(Container(1)).ap(Container(3))
//=> Container(4)

add gets partially applied to each of the calls (so add has to be curried).

###Applicatives

Same as before but with Maybe.

  Maybe.of(add).ap(Maybe(1)).ap(Maybe(3))
//=> Maybe(4)

Maybe.of(add).ap(Maybe(1)).ap(Maybe(null))
// this will not run at all
//=> Maybe(null)

###Applicatives

  var loadPage = _.curry(function(products, reviews){ render(products.zip(reviews) })

Future.of(loadPage).ap(Api.get('/products')).ap(Api.get('/reviews'))

What's great is that you can wait for two independent asynchronous calls! It will wait for both them to finish (gate).


####EventStream

  var showLoaded = _.curry(function(tweetbtn, fbbtn){ alert('Done!') })

EventStream.of(showLoaded).ap(tweet_btn.on('load')).ap(fb_btn.on('load'))

The two buttons have to be pushed to run showLoaded. You can also swith between futures and eventstreams (user interaction or socket.io for example)


Or validation. If either of them fail, it will not run:

  var getVal = compose(Maybe, pluck('value'), document.querySelector)
var save = _.curry(function(email, pass){ return User(email, pass) })

Maybe.of(save).ap(getVal('#email')).ap(getVal('#password'))
//=> Maybe(user)

Same pattern as above, exce.


So far we always chained off the of off a specific Functor. Introduces liftA2 which is type generic:

  var getVal = compose(Maybe, pluck('value'), document.querySelector)
var save = _.curry(function(email, pass){ return User(email, pass) })

liftA2(save, getVal('#email'), getVal('#password'))
//=> Maybe(user)

Important to note, that both functors have to be of the same type, but they can be swapped out to other types. But as long as they are the same types liftA2 is generic enough to work with any type.


  var loadPage = _.curry(function(ships, orders, receipts){
  return render(ships, orders, receipts)
})


liftA3(loadPage, http.get('/shipments'), http.get('/orders'), http.get('/receipts'))
//=> Future(Dom)

liftA3 takes three arguments


http://jsbin.com/perutuyijo/1

answer: answers: http://jsbin.com/rusacuqidu/1

###Exercises

jsbin.com/zowev/4/edit

  // Exercise 1
// ==========
// Write a function that add's two possibly null numbers together using Maybe and ap()
console.log("--------Start exercise 1--------")

var ex1 = undefined;

// solution :
var ex1 = function (x,y) {
  return Maybe.of(_.add).ap(Maybe(x)).ap(Maybe(y))
}

assertDeepEqual(Maybe(5), ex1(2, 3))
assertDeepEqual(Maybe(null), ex1(null, 3))
console.log("exercise 1...ok!")

  // Exercise 2
// ==========
// Rewrite 1 to use liftA2 instead of ap()
console.log("--------Start exercise 2--------")


var ex2 = undefined;

// solution

var ex2 = liftA2(_.add)

assertDeepEqual(Maybe(5), ex2(Maybe(2), Maybe(3)))
assertDeepEqual(Maybe(null), ex2(Maybe(null), Maybe(3)))
console.log("exercise 2...ok!")

liftA2 is curried and can be partially applied


  // Exercise 3
// ==========
// Make a future by running getPost() and getComments() using applicatives, then renders the page with both
var makeComments = _.reduce(function(acc, c){ return acc+"<li>"+c+"</li>"}, "")
var render = _.curry(function(post, comments){
  return "<div>"+post.title+"</div>" + makeComments(comments);
})
console.log("--------Start exercise 3--------")


var ex3 = undefined

// solution

// var ex3 = getPost(), getComments()
// then:
var ex3 = liftA2(render, getPost(), getComments())
// getPost is down in the linked jsbin file
// the point is that it returns a future

// futures take a fork method to run
ex3.fork(log, function(html){
  assertEqual("<div>Love them futures</div><li>This class should be illegal</li><li>Monads are like space burritos</li>", html)
  console.log("exercise 3...ok!")
})

  // Exercise 4
// ==========
// setup...
localStorage.player1 = "toby"
localStorage.player2 = "sally"

// Write a function that gets both player1 and player2 from the cache.
var getCache = function(x){ return localStorage[x]; }.toIO();
var game = _.curry(function(p1, p2){ return p1 + ' vs ' + p2 })
console.log("--------Start exercise 4--------")


var ex4 = undefined;
// solution
var ex4 = liftA2(game, getCache('player1'), getCache('player2'))
// these returns IOs that we apply to game

assertEqual("toby vs sally", runIO(ex4))
console.log("exercise 4...ok!")

###Applicative laws

  // identity
A(id).ap(m) == m
// composition
A(compose).ap(f).ap(g).ap(w) == f.ap(g.ap(w)))
// homomorphism
A(f).ap(A(x)) == A(f(x))
// interchange
u.ap(A(y)) == A(function(f) { return f(y) }).ap(u)

#Monoids

Combination/Accumulation

In this presentation we will focus on combination and accumulation.

functions: empty, concat, mconcat

  // Monoids

reduce(function (acc, x) {
  return acc + x
}, 0, [1,2,3])
  // Monoids

reduce(function (acc, x) {
  return acc * x
}, 0, [1,2,3])
  // Monoids

reduce(function (acc, x) {
  return acc || x
}, false, [false, false, true])
  // Monoids

reduce(function (acc, x) {
  return acc && x
}, true, [false, false, true])

Monoids are built on semigroups.

####Semigroup

anything that has a concat (combination) method

it can be plus, multiplication, etc.(see above examples)

####Monoid

Any semigroup that has an empty() method

The empty() returns a neutral element. It's the second argument to reduce.


###Monoid

  _Sum = function (v) {
  this.val = v
}
// so far this like a functor (container)
// but the val has to be a number

_Sum.prototype.concat = function (s2) {
  return Sum(this.val + s2.val)
}

_Sum.prototype.empty = function () { return Sum(0) }

There are a lot of different monoids... In your work it's very likely that you are going to be making these monoids.

  Sum(2).concat(Sum(3))
// => Sum(5)
  Sum(2).concat(Sum(3)).concat(Sum(5))
// => Sum(10)
  mconcat([Sum(2), Sum(3), Sum(5)])
// => Sum(10)

mconcat is doing a reduce function for us, and it knows the type so it knows that it has to add two numbers together.

  mconcat([Product(2), Product(3), Product(5)])
// => Sum(10)

So mconcat is generic enough to work with any type.

  mconcat([Max(2), Max(3), Max(5)])
// => Max(5)

Above, he demonstrates that these can be combined with any types.

  compose(getResult, mconcat, map(Sum))([1,2,3])
// => 6
  compose(toUpperCase, reverse])(['bonkers'])
// => 'BONKERSsreknob'

functions are monoids as well. (from the above example)


###Exercises

jsbin.com/jeqof

  // Exercise 1
// ==========
// rewrite the ex1 function to use getResult() mconcat() and Sum() instead of sum()
console.log("--------Start exercise 1--------")

var sum = _.reduce(_.add, 0)

var ex1 = sum // <----- rewrite me
var ex1 = compose(getResult, mconcat, map(Sum))

assertEqual(6, ex1([1,2,3]))
console.log("exercise 1...ok!")

  // Exercise 2
// ==========
// Similar to the above, get the Product of the list.
console.log("--------Start exercise 2--------")
ex2 = undefined
var ex2 = compose(getResult, mconcat, map(Product))


assertEqual(12, ex2([2,2,3]))
console.log("exercise 2...ok!")

  // Exercise 3
// ==========
// Similar to the above, get the Max of the list.
console.log("--------Start exercise 3--------")
ex3 = undefined
var ex3 = compose(getResult, mconcat, map(Max))

assertEqual(32, ex3([12,32,3]))
console.log("exercise 3...ok!")

  // Exercise 4
// ==========
// use the function monoid instance to mconcat the functions below to create a full name string.
console.log("--------Start exercise 4--------")
var firstName = _.get('first')
var middleName = _.get('middle')
var lastName = _.get('last')
var space = _.K(' ')

var ex4 = undefined
var ex4 = mconcat([firstName, space, middleName, lastName])

var user = {first: "Bill", middle: "Jefferson", last: "Clinton"}
assertEqual("Bill Jefferson Clinton", ex4(user))
console.log("exercise 4...ok!")

  // Exercise 5
// ==========
// For Tuple to be a monoid, it's x,y must also be monoids. Monoids beget monoids.
// Use this information to complete the definition of Tuple's concat fn.
console.log("--------Start exercise 5--------")

var Tuple = _.curry(function(x, y) { return new _Tuple(x, y) })

var _Tuple = function(x, y) {
  this.x = x;
  this.y = y;
}

_Tuple.prototype.inspect = function() {
  return 'Tuple('+inspect(this.x)+' '+inspect(this.y)+')';
}

_Tuple.prototype.empty = function() { return Tuple(this.x.empty(), this.y.empty()) };


// TODO: DEFINE ME
_Tuple.prototype.concat = function(t2) {
  // solution
  return Tuple(this.x.concat(t2.x), this.y.concat(t2.y))
};



var ex5 = mconcat([Tuple("abc", [1,2,3]), Tuple("def", [4,5,6])])

assertDeepEqual(Tuple("abcdef", [1,2,3,4,5,6]), ex5)
console.log("exercise 5...ok!")

So here we could combine two values into one (Tuple)


###Monoids

  mconcat([Failure(['message1']), Success(attrs), Failure(['message2'])])
// => Failure(['message1', 'message2'])

We subclass validation into failure and success.

  var checkValidations = mconcat([checkPassword, checkEmail, checkName])

checkValidations({name:'Burt'})
// => Failure(['need a password', 'need an emai'])

We combine several validations into one.

###Monoid laws

  // left identity
concat(empty, x) == x

// right identity
concat(x, empty) == x

// associativity
concat(concat(x, y), z) == concat(x, concat(y, z))

Explains that these above laws are exactly the same as the monad and category laws. (see below)

###Category laws

  // left identity
compose(id, f)  == f

// right identity
compose(f, id)  == f

// associativity
compose(compose(f, g), h)  == compose(f, compose(g, h))

###Monad laws

  // left identity
mcompose(M, f)  == f

// right identity
mcompose(f, M)  == f

// associativity
mcompose(mcompose(f, g), h)  == mcompose(f, mcompose(g, h))

##Fantasy Land

Fantasy land specs define the names that you should be using. Here we did not always follow these specs. The importance of fantasyland is that your code will be compatible with others so you can compose your code better.