Does JQuery expose a monadic interface?
Pam Selle raised an interesting question on her blog recently: does jQuery expose a monadic interface? I’ll interpret this as specifically asking: are jQuery containers monads? This was such an interesting question that I decided to investigate it more thoroughly; one can never have too much practice figuring out how monads work, in my experience!
There are a couple of
formulations of monads;
both have in common that there is a container type
M
that “wraps” plain data values in some fashion. Here,
M
is the type of jQuery containers, and the plain data values
are DOM elements. Some monads are polymorphic, in that they can wrap
values of a wide variety of underlying data types (lists and
Option/Maybe are good examples of these). jQuery containers are also
polymorphic, although we usually just apply them to DOM elements. For
the rest of this post, we’ll refer to C
as the type of a
jQuery container, and when it contains items of type T
then
we’ll write C[T]
.
Monadic operators
Now, in order to qualify as a monad, a type has to support certain operations with particular signatures, and then those operations have to obey certain “monadic laws” in order to be correctly composeable.
Both formulations share a requirement for a simple wrapping function
called return
or unit
that take plain data values
(DOM elements) and return values of the monadic type (jQuery
containers, or C
). In other words, we’re looking for an
operation / “constructor” of type:
unit : T → C[T]
If e
is a DOM element, then $(e)
returns a jQuery
container with that one element in it; ditto for $(a)
if
a
is an array of DOM elements. We can then understand all the
other selectors as syntactic sugar for one or the other of these. For
example, $("#foo")
is equivalent
to $(document.getElementById("foo"))
. In fact, we can
see that even just $(elt)
for a single element is equivalent
to $([elt])
(i.e. wrapping the element in a single element
array). This will be convenient for the rest of this post, since it
means we just have to deal with this simplest constructor. At any
rate, it seems like we have the unit
operation covered.
Now, in one of the formal definitions of monad, the other operation
required is one called bind
that can take a value of the
monadic type, a function from the underlying data type to another
value of the monadic type, and returns a value of the monadic type
again. That’s a mouthful, so it might be helpful to look at the type
of this operator:
bind : C[T] → (T → C[U]) → C[U]
Conceptually, this takes every contained item of type T
,
applies a transformation to it that maps to a new set of containers,
and then “squashes” it down into one container again. As is common
with object-oriented language implementations, the
this
variable can be thought of as an implicitly-passed
parameter, so we can then look through the API for a jQuery container
looking for a method that takes one of these transformation callbacks
and returns a new jQuery container. One such candidate is the
.map()
method, which
is defined as:
Pass each element in the current matched set through a function, producing a new jQuery object containing the return values.
Wow, this looks pretty good; it takes a transformation function and returns us a jQuery container at the end. The real question is whether it will “flatten” things down into a single jQuery container for us (since the documentation doesn’t say) if our function returns jQuery containers. Suppose we have the following markup embedded in a page:
<div id="example">
<div class="outer"><div id="one" class="inner"></div></div>
<div class="outer"><div id="two" class="inner"></div></div>
</div>
Now, suppose we try this in the Javascript console:
> $(".outer").map(function (idx,elt) { return $(elt).children(); });
We can see that this does, in fact, squash down to just a container
with the two div.inner
elements in it, as opposed to a
container containing two containers. Nice!
Monad Laws
We have our candidate operations for unit
and bind
, so
we have to check whether they adhere to certain properties; these
properties are similar in spirit to the commutative or associative
properties of addition (or, perhaps better, the distributive law of
multiplication over addition, since that expresses a relationship
between two operations). For the below notation, the bind
operator is written as >>=
. The first law says:
(unit x) >>= f ≅ f x
Or, to put things into jQuery syntax: $(x).map(f)
should be
the same as f(x)
. If f()
can take a DOM element and
return a jQuery container, then we can see from our test above that
we’re in good shape here. The second law says:
m >>= return ≅ m
which is to say that if m
is a jQuery container, then it
should be the case that m.map(function (idx,item) { return $(item);
})
gives us m
back again, which also seems right. Finally, the third law says:
(m >>= f) >>= g ≅ m >>= ( \x→ (f x >>= g))
Or, that if m
is a jQuery container, and f
and g
are transformer functions, that the following are equivalent:
m.map(f).map(g)
m.map(function (idx,x) { return f(x).map(g); })
Ok, that’s a mouthful–or at least a keyboardful. What we are doing in
the first line is taking each element in m
and applying
f
to it, squashing this into one container, then taking each
element of that collection and then applying g
to it,
then flattening everything down into one collection. In the second
line, we are taking each element in m
, and then applying the
given function to it, but we can see that the body of the function
does the same thing: namely, applying f
to the element (which
returns a container, remember), and then mapping g
across all
those elements. Because .map()
does squashing for us,
we end up with the equivalent containers at the end.
And that’s it! It does look like jQuery containers are a monad after
all. We can actually understand several methods of jQuery containers
as convenient applications of .map()
. For example,
.children() is
really equivalent to:
.map(function (idx,elt) {
return $(elt.childNodes);
});
[I suspect that the actual implementation of .children()
ends
up being more efficient, as it doesn’t have to construct all the
intervening jQuery containers and then squash their arrays back
together again.]
Exercise for the reader: Can you find an expression of jQuery
containers as a monad in their other
formulation,
with return
, fmap
, and join
? (I ran out of time
to try this before I had to make dinner for my kids!)
The Upshot
“So what?” you might ask. Well, it suggests that if you are writing a
library meant to be used with jQuery, you might be well served to
write many of your utility functions in the form of the monadic
transform functions we saw above, taking DOM elements and returning
jQuery containers–i.e. making sure your utility functions can be
passed as arguments to .map()
, because it means that they
will be composeable in very flexible ways that let them be chained
together easily.