Quick notes on how the fallback
pseudo-type is used in cljd (ClojureDart).
Like cljs, cljd is built on protocols. Like in clj, each protocol is backed by an interface for the fast path. Let's call this interface the direct interface.
Cljd protocols are also backed by a second interface used for extensions to 3rd-party classes. Let's call this interface the extension interface.
Protocols are reified as instances of an IProtocol interface:
// emitted code
abstract class IProtocol extends dc.Object {
dc.dynamic extension(x$1, );
dc.dynamic satisfies(x$2, );
}
At static call sites of a protocol method (let's take -count
of Counted
as an example) instead of emitting a function call to -count
we emit:
if((coll106192$1 is ICounted$iface)){ // check against the direct interface
$if_$1=(coll106192$1 as ICounted$iface).$_count$0(); // on success, cast and call the actual method
}else{
// retrieve extension object and call method on it, passing the actual object as first argument
$if_$1=ICounted.extensions(coll106192$1, ).$_count$0(coll106192$1, );
}
Now, let's have a look at the cljs implementation of count
:
(defn count
"Returns the number of items in the collection. (count nil) returns
0. Also works on strings, arrays, and Maps"
[coll]
(if-not (nil? coll)
(cond
(implements? ICounted coll)
(-count coll)
(array? coll)
(alength coll)
(string? coll)
^number (.-length coll)
(implements? ISeqable coll)
(accumulating-seq-count coll)
:else (-count coll))
0))
We see there's an explicit test against an implementation interface and then several fallbacks. In our case it would mean we would check twice against the direct interface: once in user code and once as part of the emitted dart for the -count
callsite.
By having the fallback
pseudo-type we can move all fallbacks into it, including nil
and our count
is just:
(defn count [x] (-count x))
We have removed the explicit interface check, this is already a gain but we can go further if we add :inline
metadata (like in Clojure):
(defn count
{:inline (fn [x] `(-count ~x))
:inline-arities #{1}}
[x] (-count x))
Now all static calls to count
become calls to -count
(here it's trivial but for other methods, arguments order are shuffled by the inlining) and are thus "inlined" as a protocol method callsite and not a regular function call.