Fork me on GitHub
#squint
<
2022-08-22
>
Prabhjot Singh05:08:01

# Suggestion to handle multi-arity functions When you create a function in javascript it gets a length property which is the number of arguments. For Example

function x(a,b,c){

}

console.log(x.length) // 3


function y(a, b, ...c) {

}
console.log(y.length) // 2
length does not include vararg. But that can be handled by adding a custom tag. We can use the above information to generate multi arity functions for calva
(defn foo
     ([a] "Arity 1")
     ([a b] "Arity 2")
     ([a b & args] "Arity 2 + Vararg"))

(println (foo 1))
(println (foo 1 2))
(println (foo 1 2 3))
Can be compiled to
export const foo = multi(
  "foo",
  function foo(a) {
    return "Arity 1";
  },
  function foo(a, b) {
    return "Arity 2";
  },
  add_vararg_tag(function foo(a, b, ...args) {
    return "Arity 2 + Vararg";
  })
);

console.log(foo(1))                  // Arity 1
console.log(foo(1, 2))               // Arity 2
console.log(foo(1, 2, 3))            // Arity 2 + Vararg
Following is a how the multi and add_vararg_tag can look like
function add_vararg_tag(f) {
  f["vararg"] = true; // there are many ways to do this, JS Symbols could be used
  return f;
}

function multi(name, ...funcs) {
  const varargFunc = funcs.filter((f) => f["vararg"])[0];

  const m = {};
  for (let f of funcs) {
    if (f !== varargFunc) {
      m[f.length] = f;
    }
  }

  return function (...args) {
    var f = m[args.length];

    if (f) {
      return f(...args);
    }

    if (varargFunc && args.length >= varargFunc.length) {
      return varargFunc(...args);
    }

    throw `Arity ${args.length} is not supported by ${name}`;
  };
}

borkdude06:08:22

@UGFGYK4GM Do you mean, instead of the current multi-arity implementation? What would be the benefit of the above vs the current implementation?

borkdude06:08:26

(The current implemenation comes from CLJS)

borkdude06:08:45

$ ./node_cli.js -e '(do (defn foo ([] :zero) ([_] :one)) (prn (foo))) (prn (foo 1))'
"zero"
"one"

Prabhjot Singh06:08:14

the main advantage is that there is less code generated.

Prabhjot Singh06:08:37

so the output is smaller

borkdude06:08:33

Can you put the above in a Github Discussion? Bundle size is important, but so is performance, so that has to be measured as well

Prabhjot Singh06:08:51

I beleive there could be a runtime performance gains as well with the new approach but that may not be true.

Prabhjot Singh06:08:36

yes I agree, performance is important and I have not done any performance testing yet.

borkdude06:08:13

The output for multi-arity with fixed args is already not so big, the biggest win could be made with using the spread operator for varargs

borkdude06:08:57

which should probably be handled as a separate issue

borkdude06:08:24

there is a cherry issue here: https://github.com/clavascript/cherry/issues/31 but we have to have one for clava too

Prabhjot Singh06:08:08

ok I will look at that. Also, the other motivation was to make it easier to write muti-arity functions directly in javascript. For example for the core functions.

borkdude06:08:03

I would prefer not to use multi-arity for core functions, so we don't have to do extra work performance-wise

Prabhjot Singh07:08:51

Now I realise that the multi-arity problem is not as bad as I thought. As you explained it is only an issue with vararg. But I will create an issue in Github anyway for reference and further discussion.

Prabhjot Singh08:08:03

Ok now I am convinced that this is actually not a better idea. But I have submitted an issue to suggest some improvements to the current code gen. https://github.com/clavascript/clavascript/issues/114

Chris McCormick09:08:29

i'm excited to build some software using this at some point, thank you all for your work on it. 🙏

🎉 1
Prabhjot Singh10:08:07

@borkdude are there plans to support calling a data structure like a function. Like following ({:a :b} :a)

borkdude10:08:44

Only if this won't impact bundle size and performance negatively. Keyword as function is supported right now but only in direct call position

Prabhjot Singh10:08:26

clojureScript accomplish this by calling .call() on the datastructure. This works because all the js functions already have a method call on them. If we want to do the same in clava then we will need to add the call method on {} and [] etc. In the past people use to achieve this by changing the prototype but now a days it is not regarded as a good idea. It is not good for tree shaking and for many other reasons. Do you have any other ideas.

borkdude11:08:28

Yes, might not be worth it. Just use get

lilactown16:08:02

using symbols as protocols means that we can create a thin wrapper around JS' iterable protocol and start extending it to our own types using extend-type

lilactown16:08:06

// core.js
export const IIterable = Symbol('clava.core/IIterable');

export const IIterable_iterator = Symbol.iterator;

export const iterator = es6_iterator;
(deftype Foo []
  IIterable
  (iterator [_]
    (iterator [1 2 3])))

(map inc (->Foo)) ;; => [2 3 4]

👍 2
borkdude16:08:08

wouldn't this be a conflict/recursive call by accident?

(iterator [_]
    (iterator [1 2 3])))

lilactown16:08:57

rn, the extend-type of this expands to

(Foo["prototype"][IIterable] = true);
(Foo["prototype"][IIterable_iterator] = function () {
var self__ = this;return iterator([1, 2, 3]);
});

lilactown16:08:43

actually I'm not sure I understand your question

borkdude17:08:06

never mind, the misunderstanding was mine :)