squint icon indicating copy to clipboard operation
squint copied to clipboard

Multi-Arity + vararg Improvements

Open prabhjots opened this issue 2 years ago • 2 comments

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.

prabhjots avatar Aug 22 '22 07:08 prabhjots

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.

lilactown avatar Aug 23 '22 16:08 lilactown

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.

borkdude avatar Sep 04 '22 12:09 borkdude