Fairmont's Streamlined Multimethods
Fairmont is a JavaScript library for functional reactive programming. Fairmont wants you to be able write polymorphic functions that you can still compose, curry, and, generally, use like ordinary functions. So Fairmont provides multi-methods. In the latest release of Fairmont, we’ve made them faster and simpler.
Multi-methods Recap
We’ve written about multi-methods before.
To recap, multi-methods make it possible to dispatch on the type of one more arguments.
For example, here’s Fairmont’s implementation of map
using multi-methods.
map = Method.create()
Method.define map, isFunction, isDefined,
(f, x) -> map f, (producer x)
Method.define map, isFunction, isIterator, (f, i) ->
iterator ->
{done, value} = next i
if done then {done} else {done, value: (f value)}
Method.define map, isFunction, isReactor, (f, r) ->
reactor ->
(next r).then ({done, value}) ->
if done then {done} else {done, value: (f value)}
Streamlined And Simplified
We’ve streamlined and simplified things in the latest release.
Method definitions match on predicates—functions that return true
or false
—instead of allowing types or values.
You can still use predicates to check for those things, as shown the map
example.
We dispatch based on the first match—a definition whose predicates match their corresponding arguments. We test them in LIFO order, meaning we try the last definition first. This means you should declare more general cases first, and specializations last.
This approach has two advantages. First, it’s easier to reason about since the dispatch rules are simpler. Second, it’s much faster, for much the same reason. Before we had to check for a match based on value, type, or the prototype chain, for every argument, and we had a precedence associated with each possible match—ex: values trumped types.
We chose predicates because they don’t impose a particular programming model.
Want to use duck typing?
Just define corresponding predicates.
Want to match based on value?
Just use Fairmont’s eq
function.
Type-Matching Predicates
For type-based matching, Fairmont provides isType
and isKind
which use the new Object.getPrototypeOf
function in ES6
.
Since they’re curryable, it’s easy to write your own custom predicates and then use them in multi-methods.
isShape = isKind Shape
isRectangle = isKind Rectangle
isEllipse = isKind Ellipse
area = Method.create()
Method.define area, isRectangle, (r) -> r.height * r.width
Method.define area, isEllipse, (e) -> e.height * e.width * Math.PI
Remember, though, ancestor prototypes no longer automatically have precedence over their descendants. The only precedence is the order of declaration.
In the future, we expect to add improved logging to ease debugging and a way to override the default precedence. For now, though, we have faster, simpler multi-methods. Which, in turn, give us polymorphic functions without having to fall back to (more limited) objects and methods.