Disfunctional RSS

Where misquotes, misinformation, and misspellings occur daily.

Archive

Jun
18th
Mon
permalink

Functor, I hardly knew her!

I’m here! I’m at that special moment where I just realized why the heck functors matter. At first I knew the types and I knew the ideas, I just didn’t understand why it mattered.

I think the root of my misunderstanding was my OO background and confusion about the job of types in the functional realm. I’m gonna write a post about that tomorrow, promise.

The big revelation came when I was using flapjax (flapjax-lang.org). There is an object called Behavior that holds a value. To run a normal function on that value, you have to call liftB. That will then pull the value out of the behavior, run your function with it, then put it back in. Like this:

var myBehavior = new Behavior("Jimmy");
var updateUI = function(name){ $("#username").text(name) }
var newBehavior = liftB(updateUI, myBehavior);
Flapjax is awesome, but nevermind that right now - the details aren’t important. The important part is that the Behavior is actually a Functor. And liftB should just be fmap.

This blew my mind because I realized how many things out there are actually functors. Let’s dig in and then maybe look back at this code and hopefully you can see what I mean (if i do a good job of explaining it).

So what’s up with Functors?

Lets forget about the mathematical aspects for a bit. And besides, I’m not the guy to explain that anyways.

What a functor does is let you run a normal function that expects a normal type, like a string or integer, with a fancy type, like an object. A nice way to say that is you map a function over a type.

Let’s start with a brilliant example.

We have an object/type that’s called Observable. I’m using objects and types interchangeably here.


var Observable = function(x) {
  return {val: x};
}

var o_name = Observable("brian");
Admittedly, it’s not that observable just yet.

We’d really like to run a normal function like reverse on our brand new observable, but we’ve stuck our string “brian” inside of this object and reverse doesn’t work on that object. Enter fmap.

fmap is a universal name (just like map) for mapping a function over a type. I’m guess it stands for function map or functor map. Since functions are sometimes called maps in category theory, I guess function map would be redundant. But anyways.

Let’s make our object a functor by giving it an fmap implementation.


var Observable = function(x) {
  
  var fmap = function(f) {
    var new_val = f(x);
    if(new_val != x) fireEvent('changed!', {val: new_val, previous_val: x})
    return Observable(new_val);
  }
  
  return {val: x, fmap: fmap};
}
There we go. Check it out. So we take a function, and get new_val by running it over our previous value (in this instance “brian”). Then if the two values aren’t the same, we fire an event that it was changed with some information passed along in the event about old and new values. Finally, and this is the important part, we wrap our new value back in our Observable and viola! we’ve run a normal function on our type.

Since fmap is kind of universal, we can define a nice function that will work on any functor instance (that is, any instance that implements fmap).

var fmap = function(f,o) {
  return o.fmap(f);
}
And our final code is this:

var o_name = Observable("brian");
fmap(reverse, o_name); // Observable("nairb")
And there you have it!

Part of what’s great about this is our ability to run normal everyday functions on our objects. We needn’t define the same old stuff from object to object as long as we implement a way to map (fmap) over it. Another reason you’ll want to wear your Functor pants to work tomorrow is that we’ve got the ability to actually alter the functions behavior while running inside our object.

Let’s look at one more example. It’s our old friend Maybe.

var Maybe = function(x) {
  var fmap = function(f) {
    return isFalsy(x) ? Maybe(null) : Maybe(f(x));
  }
  
  return {val: x, fmap: fmap};
}

//+ getProduct :: Number -> Maybe(Product)
var getProduct = compose(Maybe, db.find('products'));

//+ nameOfProduct :: Number -> Maybe(String)
var nameOfProduct = fmap(pluck('name'), getProduct);

nameOfProduct(3) // "hella t-shirt"

Here, if the product doesn’t come back, we won’t blow up when we read it’s name. Maybe just checks to see if the value’s there before it runs a function on it.

What’s more, we could make db.find return a functor too. We could say the call itself is an instruction to get the record and by fmapping something over it, it will actually make the db call.

In conclusion

We’re really just abstracting function application. Any object could be a functor, but you usually want to stick with a behavior type object with a single value.

. The technical term for this stuff is that we’re lifting a function into a computational context. That makes sense since the context can change the behavior of our normal functions. In the case of Observable it adds behavior by notifying the authorities of change. In the case of Maybe it might not run it at all.

So the intuition is that you can run a normal function on the value(s) inside a “container” object or you can run a function in a particular context.

There are some laws that you must follow. Really you should just make sure that fmapping the identity function (function(x) { return x; }) is equal to just calling the identity function.

  fmap(id) == id;
I guess the last thing I wanted to say was that a List is a functor. fmap in the cast of list is just our beloved map function that runs a normal function on each element inside of the list. So the container analogy works well here too.

Well, that’s all I’ve got for now. Goodnight sweet Tumblr.
blog comments powered by Disqus