squint
squint copied to clipboard
Multi-Arity + vararg Improvements
When skipping an arity
The following function does not have an arity 2
(defn foo
([a] "1")
([a b c] "3")
([a b c & args] "3 and more"))
In Clojure
Clojure 1.11.1
user=> (defn foo ([a] "1") ([a b c] "3") ([a b c & args] "3 and more"))
#'user/foo
user=> (foo 1 2)
Execution error (ArityException) at user/eval137 (REPL:1).
Wrong number of args (2) passed to: user/foo
ClojureScript also behaves the same
In Clava
./node_cli.js -e '(do (defn foo ([a] "1") ([a b c] "3") ([a b c & args] "3 and more")) (prn (foo 1 2)))'
"3 and more"
An Error was expected but instead vararg variant is invoked.
We do get an error if vararg is not there.
Vararg performance
Consider the following example
(defn foo
([a] "1")
([a & args] "vararg"))
Following is part of the code which is generated
let f44 = function (var_args) {
let G__4849 = arguments["length"];
switch (G__4849) {
case 1:
return f44.cljs$core$IFn$_invoke$arity$1(arguments[0]);
break;
default:
let args_arr4651 = [];
let len__22086__auto__52 = arguments["length"];
let i4753 = 0;
while (true) {
if (i4753 < len__22086__auto__52) {
args_arr4651.push(arguments[i4753]);
let G__54 = i4753 + 1;
i4753 = G__54;
continue;
}
break;
}
let argseq__22178__auto__55 =
1 < args_arr4651["length"] ? args_arr4651.slice(1) : null;
return f44.cljs$core$IFn$_invoke$arity$variadic(
arguments[0],
argseq__22178__auto__55
);
}
};
f44["cljs$core$IFn$_invoke$arity$1"] = function (a) {
return "1";
};
f44["cljs$core$IFn$_invoke$arity$variadic"] = function (a, args) {
return "vararg";
};
f44["cljs$lang$applyTo"] = function (seq56) {
let G__5758 = first(seq56);
let seq5659 = next(seq56);
let self__22117__auto__60 = this;
return self__22117__auto__60.cljs$core$IFn$_invoke$arity$variadic(
G__5758,
seq5659
);
};
f44["cljs$lang$maxFixedArity"] = 1;
the problometic part is the default case in the switch statement.
Using the new es6 spread feature the variadic function could be rewritten as
f44["cljs$core$IFn$_invoke$arity$variadic"] = function (a, ...args) {
return "vararg";
};
the diffrence is the ...
before the args
then the default case in the switch could be rewriten as
let f44 = function (...var_args) {
let G__4849 = var_args["length"];
switch (G__4849) {
case 1:
return f44.cljs$core$IFn$_invoke$arity$1(var_args[0]);
break;
default:
if(G__4849 > f44["cljs$lang$maxFixedArity"]) {
return f44.cljs$core$IFn$_invoke$arity$variadic(...var_args)
}
throw `Wrong number of args (${G__4849}) passed to: user/foo`
}
};
also f44["cljs$lang$applyTo"]
could be simplified in a similarly.
I think this looks promising. I wonder if we could inline the different arities too, since I doubt we would generate code that would directly invoke each arity method.
I created two varargs functions here:
https://github.com/clavascript/clava-immer/blob/main/index.cljs
which gives this (horribly long) output:
https://github.com/clavascript/clava-immer/blob/main/index.mjs
It would be a nice goal to reduce that output.