This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-22
Channels
- # aws (12)
- # babashka (24)
- # beginners (51)
- # biff (2)
- # cherry (4)
- # cider (2)
- # clj-kondo (4)
- # cljs-dev (19)
- # clojure (70)
- # clojure-australia (4)
- # clojure-europe (39)
- # clojure-nl (4)
- # clojure-norway (6)
- # clojure-spec (9)
- # clojurescript (21)
- # component (6)
- # cursive (18)
- # data-science (9)
- # datomic (18)
- # events (2)
- # expound (4)
- # fulcro (15)
- # graalvm (2)
- # graphql (5)
- # jobs (1)
- # juxt (2)
- # leiningen (8)
- # malli (4)
- # meander (21)
- # nrepl (3)
- # observability (14)
- # off-topic (49)
- # other-languages (1)
- # pathom (13)
- # pedestal (7)
- # rdf (5)
- # re-frame (10)
- # reitit (1)
- # sql (4)
- # squint (30)
- # tools-deps (1)
- # vim (11)
# 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}`;
};
}
@UGFGYK4GM Do you mean, instead of the current multi-arity implementation? What would be the benefit of the above vs the current implementation?
$ ./node_cli.js -e '(do (defn foo ([] :zero) ([_] :one)) (prn (foo))) (prn (foo 1))'
"zero"
"one"
the main advantage is that there is less code generated.
so the output is smaller
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
I beleive there could be a runtime performance gains as well with the new approach but that may not be true.
yes I agree, performance is important and I have not done any performance testing yet.
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
there is a cherry issue here: https://github.com/clavascript/cherry/issues/31 but we have to have one for clava too
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.
I would prefer not to use multi-arity for core functions, so we don't have to do extra work performance-wise
make sense.
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.
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
i'm excited to build some software using this at some point, thank you all for your work on it. 🙏
@borkdude are there plans to support calling a data structure like a function. Like following ({:a :b} :a)
Only if this won't impact bundle size and performance negatively. Keyword as function is supported right now but only in direct call position
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.
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
// 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]
wouldn't this be a conflict/recursive call by accident?
(iterator [_]
(iterator [1 2 3])))