In this magic post, we showed how to make a type in clojurescript behave like a function by extending the IFn protocol.

In this secret post, we unveiled defprotocol secret in clojurescript by deep diving into its transpiled javascript code.

Here, we’d like to connect the two: the Magic and the Secret.

The Astonishment

defprotocol defines a set of named methods and their signatures. Let’s look at the IFn protocol definition in clojurescript and ask ourselves the following question:

What set of named methods and signatures are defined by IFn?

(defprotocol IFn
  "Protocol for adding the ability to invoke an object as a function.
  For example, a vector can also be used to look up a value:
  ([1 2 3 4] 1) => 2"
    [this a]
    [this a b]
    [this a b c]
    [this a b c d]
    [this a b c d e]
    [this a b c d e f]
    [this a b c d e f g]
    [this a b c d e f g h]
    [this a b c d e f g h i]
    [this a b c d e f g h i j]
    [this a b c d e f g h i j k]
    [this a b c d e f g h i j k l]
    [this a b c d e f g h i j k l m]
    [this a b c d e f g h i j k l m n]
    [this a b c d e f g h i j k l m n o]
    [this a b c d e f g h i j k l m n o p]
    [this a b c d e f g h i j k l m n o p q]
    [this a b c d e f g h i j k l m n o p q r]
    [this a b c d e f g h i j k l m n o p q r s]
    [this a b c d e f g h i j k l m n o p q r s t]
    [this a b c d e f g h i j k l m n o p q r s t rest]))

The answer seems to be trivial: 22 flavours of -invoke.

It becomes less trivial when you pay attention that a type that implements -invoke is callable as a function with no mention of -invoke.

For instance:

(:a {:a "A"})

The purpose of this article is to show you what happens behind the scenes with IFn and defprotocol.


The Magic

In clojure[script], there are two kinds of function calls:

  1. regular function call e.g. (fn x y z)
  2. with apply e.g. (apply fn [x y z])

Let’s see how the clojurescript compiler translates for the function = the two kinds of function calls:

So nice! All the function calls in clojurescript are routed to either call or apply.

Therefore, in order to make a type behaves like a function in clojurescript, we simply need to add implementation for call and apply to the type Prototype.

Let’s see how to implement call and apply for RegExp type in javascript:

See the Pen XdZPZm by Raphael Boukara (@raphaelboukara) on CodePen.

Now, let’s observe the transpiled javascript code for extend-type and IFn:

The compiler - not like for regular protocols - created code for call and apply into RegExp prototype.

This is the magic that allows types to behave like functions in clojurescript.

(The compiler also created code for cljs$core$IFn$_invoke$arity$1 and I don’t know why. If someone knows the reason, please add a comment below.)

The Secret

Now that you have seen the Magic, you probably wonder how it works.

It’s time to uncover the Secret of IFn:

There is a special treatment for IFn in the code of the extend-type macro:

Here is the piece of code used by extend-type to behaves differently when the implemented protocol is IFn:of the extend-type macro:

(if (= psym 'cljs.core/IFn)
  (add-ifn-methods type type-sym sig)
  (add-proto-methods* pprefix type type-sym sig))

Feel free to deep dive into the code of extend-type, proto-assign-impls and add-ifn-methods and to share your insights in the comments below.

Clojurescript rocks!