:lite-mode is looking pretty interesting, 7K brotli / 8K gzip for println + vector
but if we use the same strategy for map and vector and set - I don't think it will grow much beyond that
that is just loading the original data structure implementations from 2011 w/ whatever fixes or modifications they might need
low impact because it's a compiler flag, and adding the old implementation isn't really all that much code
idea is to make the compiler just do something for literals - and then as people play around w/ it - could do compiler pass stuff to fix all cases.
of course if you skip printing because you're just building something simple - initial size will be much smaller than 7K, more like 3K before compression for a vector, using hash maps and sets won't be adding much either
how do you feel about adding access to newer JS features such as spread/rest params? I mean a lot of code is "wasted" on juggling arguments?
code like this is pretty common
zm = function (a) {
switch (arguments.length) {
case 6:
return Am(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
arguments[4],
arguments[5]
);
case 7:
return ym(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
arguments[4],
arguments[5],
arguments[6]
);
default:
throw Error(["Invalid arity: ", v.g(arguments.length)].join(""));
}
};zm = function (...a) {
switch (a.length) {
case 6:
return Am(...a);
case 7:
return ym(...a);
default:
throw Error(["Invalid arity: ", v.g(a.length)].join(""));
}
};dunno how that actually compares after gzip, but might be worth investigating
I don't really love the idea of relying on newer JS features (we currently have a mistake in the codebase I need to address), it has to be a big win. I did play around w/ sharing the error strings but compression showed it made no difference at all. for repeated stuff like this probably compression fixes it for us.
But, not against considering it if someone wants to do a code size assessment
I implemented this for squint just now: https://clojurians.slack.com/archives/C07UQ678E/p1753867547957739?thread_ts=1753867469.711179&cid=C07UQ678E
but I think it will break applying a function to an infinite sequence.
e.g.: (apply (fn [x & xs] x) (range))
so I'll likely have to revert this change
should only use the spread thing for fixed multi-arity fns. variadic stuff must have special handling precisely because of apply and seqs
👍
this will complicate the code somewhat though. the outer function:
zm = function (...a)
can only do this when all the arities are fixed but when there is one varargs, you must fall back to the old implhmm why? the variadic bit currently has to do this array copy to get convert arguments, which is then no longer necessary?
demo.browser.x = function demo$browser$x(...a) {
switch (a.length) {
case 1:
return demo.browser.x.cljs$core$IFn$_invoke$arity$1(...a);
break;
case 2:
return demo.browser.x.cljs$core$IFn$_invoke$arity$2(...a);
break;
default:
var argseq__5775__auto__ =
2 < a.length
? new cljs.core.IndexedSeq(a.slice(2), 0, null)
: null;
return demo.browser.x.cljs$core$IFn$_invoke$arity$variadic(
a[0],
a[1],
argseq__5775__auto__
);
}
};I mean this would need to be benchmarked to see if the ...a spread is even usefull perf wise. could just stick with a[0] etc, but since the rest thing creates a proper array it should make handling actually easier
demo.browser.x = function demo$browser$x(var_args) {
var G__45643 = arguments.length;
switch (G__45643) {
case 1:
return demo.browser.x.cljs$core$IFn$_invoke$arity$1(arguments[0]);
break;
case 2:
return demo.browser.x.cljs$core$IFn$_invoke$arity$2(
arguments[0],
arguments[1]
);
break;
default:
var args_arr__5774__auto__ = [];
var len__5749__auto___45666 = arguments.length;
var i__5750__auto___45667 = 0;
while (true) {
if (i__5750__auto___45667 < len__5749__auto___45666) {
args_arr__5774__auto__.push(arguments[i__5750__auto___45667]);
var G__45668 = i__5750__auto___45667 + 1;
i__5750__auto___45667 = G__45668;
continue;
} else {
}
break;
}
var argseq__5775__auto__ =
2 < args_arr__5774__auto__.length
? new cljs.core.IndexedSeq(args_arr__5774__auto__.slice(2), 0, null)
: null;
return demo.browser.x.cljs$core$IFn$_invoke$arity$variadic(
arguments[0],
arguments[1],
argseq__5775__auto__
);
}
};
compared to this currently I meanheck, given that its a proper array we might even be able to change the variadic impl to something like
demo.browser.x = function demo$browser$x(...a) {
switch (a.length) {
case 1:
return demo.browser.x.cljs$core$IFn$_invoke$arity$1(...a);
break;
case 2:
return demo.browser.x.cljs$core$IFn$_invoke$arity$2(...a);
break;
default:
return demo.browser.x.cljs$core$IFn$_invoke$arity$variadic(a, 2);
}
};
I have spent no time on this, so definitely requires more thought, but I'm fairly confident that it simplifies a lot of code overallWhat I mean is, the outer function has:
demo$browser$x(...a)
wouldn't this convert the infinite lazy seq to an array, which isn't possible in finite time and space?no? I'm confused, not actually sure what you mean.
let's just start with this example:
(apply (fn [x & xs] x) (range))
how would this be handled by the above?
since a range doesn't have a length property, I guess it would hit the default case which then works properly with seqs?
I think I got it thenyeah that hits the applyTo helper first, which extracts the first two args and calls the $variadic fn. so should be fine.
yeah there's still a way to go (not clear what bumps I'm gonna hit here), buuuuut .... in master which has a large number of changes for code size, 8K brotli for just a vector, 1K brotli in the :lite-mode branch
so people building simple interactive stuff, the cost of CLJS just went waaaaaay down.
3K vs 88K for a simple hash-map in the :lite-mode branch
nice work. does equality and hashing etc still work the same way?
it's all protocols so if it's implemented yes
but how do you explain the difference? I mean PersistentHash/ArrayMap are not 85K of code?
oh but actually they are
PHM needs PAM needs Vector needs ChunkedSeq ...
once you hit PHM you pull in everything
PAM pulls in PHM
and the Transient impls
and MapEntry - anyways you get the idea
right ok
so it's a mess, so using the old stuff means you just avoid all that
so I see two goals w/ :lite-mode
1. removing DCE issues remaining in the standard lib beyond data structures
2. gauging what people actually need/tolerate in :lite-mode - like just not implementing toString
2 implies giving up on full fidelity, perhaps even compatibility in some cases - maybe that's fine - not going to figure that on day 1 or even probably in a year
so :lite-mode might be for people who just don't care about dependencies - they just want to write some Clojure, get a tiny artifact and go do something else
@dnolen squint was also made to get very small artifacts, so you can actually publish your "CLJS" library to npm without bringing the whole data structure lib + part of the stdlib into it. one extra consideration with squint (and cherry) is that two or more libraries can share the same stdlib - this is done by compiling against a standard library which it itself a library on npm of which you get just one copy. I understand that this may not be a goal of lite-mode, since you want to have nothing to do with npm.
I mean some knobs to tweak the size would definitely be nice. I already have a few in shadow-cljs to shave a few pct here and there.
definitely! this is a big knob for that - it might open the door for others.
I think w/ some sweat thought we can get CLJS <10K compressed for lots of simple programs
squint is also copy-on-write. you will definitely notice the difference e.g. when doing Advent of Code puzzles where in some cases where you're updating a map a million times
Haha, I know, I literally copied this stuff from 2011 commit 🙂 I remember how "fast" CLJS was back then
one of the big benefits here is going to be much like the last exercise - "why does the code size suddenly explode?"
but now you don't have all these persistent data structures that refer to each other muddying the waters
that said - I think getting :lite-mode to parity is going to be a long haul and a non-goal for shipping an experimental flag
it's looking more and more promising
just doing some trivial map, filter, range etc leads sub 10K brotli (like 5-8K)
add the data structures and you're still around 10K
needs a lot more testing, but overall I think this is really cool and overall compelling story
you can just play around w/ :lite-mode and have fun - the cost is marginal if not better than JS alternatives and you don't have to think about depenendencies at all
I was playing around goog.ui.Popup the other day and the positioning stuff, after having used some React stuff I was much more impressed w/ the decomplected nature of GCL
stepping back even further, WebComponents sans Shadow DOM by tying together some GCL is look really cool to me - and diffing whatever is completely optional and can work.