javascriptfunctional-programmingfantasyland

Understanding Fantasyland `ap`


I'm trying to come to an understand of ap, but having trouble.

In fantasyland, James Forbes says:

First we teach a function how to interact with our type, by storing that function in a container just like any other value. ( Functions are values too ya know! )

var square = Type.of(
    a => a * a
)

//=> Type (number -> number)

Then we can apply that contained function to a contained value.

square.ap( Type.of(3) )

//=> Type(9)

ap calls map on a received type, with itself as the transform function.

function ap(type){
  // recall our value
  // is a function
  // Type ( a -> a )
  var transformer = this.__value

  return type.map(transformer)
}

So this looks like ap only works if the value in our container is a function. This already feels weird to me because I thought the the whole point of The Perfect API was that these functions work on everything every time.

I also want to take note than because of the line square.ap(Type.of(3) ), it looks to me like ap takes any functor (implementer of map).

Now if I jump over to the javascript fantasy-land spec, which I assume is based on the James Forbes link, the 1.i definition of the ap signature (a.ap(b)) states

If b does not represent a function, the behaviour of ap is unspecified.

So it sounds like this spec is expecting ap to take a function unlike The Perfect API.

In summary, I guess I don't understand specifications for ap or what implementing it would look like. When I try googling this, it seems like most people just want to talk about map, which is easy for me to understand already.


Solution

  • The FantasyLand spec pre-dates James Forbes' article by three years, and was created by Brian McKenna, so it would seem that James Forbes' article is based on the spec, not the other way around.

    To answer your question, a and b must both be the same kind of "container". If a is a Maybe, then b must also be a Maybe. If a is a Task, then b must also be a Task.

    This is indicated here in the FantasyLand spec:

    b must be same Apply as a.

    Additionally, one of them must contain a function as its inner value. Which one needs to contain a function depends on the API. In the FantasyLand spec, it's b that would contain the function:

    b must be an Apply of a function

    In James Forbes' article, it's the opposite. I suspect this is because he's basing his article around Ramda, which takes arguments in the opposite order of what you would typically see elsewhere in JavaScript.

    In any case, the result of ap is a value with the same type of container as a and b:

    The Apply returned by ap must be the same as a and b

    and the result contains the result of applying the contained function to the other contained value.

    So if a were some value T[x] and b were some value T[f], then a.ap(b) would be T[f(x)].

    Hopefully that makes some sense.