;; this is a workaround: currently a page with only transpiled snippets dont work
(+ 1 2)
Function call is really the most fundamental piece of functional programming. This is the reason why we are writing so many articles on this topic:
- Extend IFn protocol like if you were part of clojurescprit core team
- Clojurescript defprotocol’s secret
- IFn’s magic beyond defprotocol secret
In this article, we present the differences between static and dynamic dispatch in clojurescript
.
The Power of call
in Javascript
As we explained it in this article, javascript’s call
mechanism is very powerful, as it allows any object to behave like a function.
Performance issues with call
The problem is that call
causes severe performance issues as reported in CLJS JIRA and in WebKit Bugzilla.
At its beginning, KLIPSE’s performances were very poor on Safari and our blog was completely broken on iOS devices. We couldn’t use advanced compilation as it is not supported in self hosted clojurescript
.
The solution: static-fns
Mike Fikes informed us that :static-fns
solves performance issues for self-host on Safari.
Here is the description of :static-fns
as it appear in clojurescript wiki page:
:static-fns employs static dispatch to specific function arities in emitted JavaScript, as opposed to making use of the call construct.
Defaults to false except under advanced optimizations.
Useful to have set to false at REPL development to facilitate function redefinition, and useful to set to true for release for performance.
This setting does not apply to the standard library, which is always compiled with :static-fns implicitly set to true.
Since Apr 8 2016, KLIPSE is compiled with :static-fns true
and our Safari readers are happy.
If you are curious about what could break the static dispatch mechanism, read this article by Mike Fikes
Examples
The good news for us - the KLIPSE fans - is that the static-fns
option is available in self host. It’s available in KLIPSE via the static-fns
url parameter. Here is the full list of KLIPSE url parameters.
Now, let’s explore the effects of static-fns
by playing with live code.
You might find it more convenient to open KLIPSE with static-fns=false or KLIPSE with static-fns=true in another window.
Here is a kind of hello world program compiled without static dispatch.
First let’s look how a multi-arity function is transpiled:
(defn foo
([] "foo")
([x] x)
([x y] y))
And now, for the function calls:
(foo)
(foo 1)
(foo 1 2)
(foo 1 2 3)
Without static dispatch, the compiler emits the same code for all the calls to foo
no matter what is the arity: cljs.user.foo.call(null,...)
. The proper function is calculated at run time. Actually, this is exactly what cljs.user.foo
does: it dispatches to the proper arity function!
Now, let’s see what happens with static dispatch:
(foo) (foo 1) (foo 1 2) (foo 1 2 3)
With static dispatch, the compiler emits code that calls the proper arity for foo
:
- 0-arity =>
cljs.user.foo.cljs$core$IFn$_invoke$arity$0
- 1-arity =>
cljs.user.foo.cljs$core$IFn$_invoke$arity$1
- 2-arity =>
cljs.user.foo.cljs$core$IFn$_invoke$arity$2
And for 3-arity, it falls back to cljs.user.foo((1),(2),(3))
as foo
doesn’t implement 3-arity.
Behind the scenes: static dispatch
Static dispatch is done by the clojurescript
compiler inside the function cljs.compiler.emit.
Here is the part that does the static dispatch for the case where the arity is completly known at compile time: the compiler checks whether there is an implementation for the arity invoked in the expression:
;; direct dispatch to specific arity case
:else
(let [arities (map count mps)]
(if (some #{arity} arities)
[(update-in f [:info]
(fn [info]
(-> info
(assoc :name (symbol (str (munge info) ".cljs$core$IFn$_invoke$arity$" arity)))
;; bypass local fn-self-name munging, we're emitting direct
;; shadowing already applied
(update-in [:info]
#(-> % (dissoc :shadow) (dissoc :fn-self-name)))))) nil]
[f nil]))))
What else?
I really encourage you to play with static-fns=true
in KLIPSE and to try to find other cases that might break the compiler static dispatch.
In another post, we will explain how static dispatch mechanism works for IFn
protocol extension.
Meanwhile, please share your thoughts in the comments below…
Clojurescript rocks!