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"
(-invoke
[this]
[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:
- regular function call e.g.
(fn x y z)
- 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 theextend-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!