any similar cheat to get native nullish coalescing operators 😬 Sure some-> exists but it compiles to a pretty heavy-looking iife (plus slightly different semantics around handling undefined)
I played around some more and made str go through a macro:
(core/defmacro stringify [& xs]
(let [args (map (fn [expr]
(cond (constant? expr)
[(->str expr) nil]
(nil? expr)
["" nil]
:else ["${(~{}) ?? ''}" expr])) xs)]
`(~'js*
~(str "`" (str/join "" (map first args)) "`")
~@(keep second args))))
$ ./node_cli.js --show -e '(str 1 (+ 1 2 3) :foo nil 3 "dude")'
import * as squint_core from 'squint-cljs/core.js';
`1${((1) + (2) + (3)) ?? ''}foo3dude`;
This spits out a template macro (because strings in str can contain newlines I had to use the template) and optimizes for literal strings, keywords or numbers.
It still adds ?? '' to make nil and undefined appear as empty strings.This is still in a branch but performance is much better this way, so thanks for the idea
give me an example please, preferably a link to the playground
I think I had support for nullish somewhere
Ah this is what squint has, support for ?? :
https://squint-cljs.github.io/squint/?src=KGpzLT8%2FIG5pbCBqcy91bmRlZmluZWQgMSk%3D
something like this: https://squint-cljs.github.io/squint/?src=KHNvbWUtPiBldnQgLi1rZXkgKC50b2xvd2VyQ2FzZSkp in an event handler where idiomatic js might be evt?.key?.toLowerCase()
I guess we could add something like:
(?.. {:foo :bar} -foo -bar (dude))I was thinking along the lines of (.?-foo obj) instead
the problem here is that objects may have fields starting with a question mark
or methods
yeah but that would be .-?foo
or follow the js syntax and put the ? before the dot, although that has its own problems
fwiw I ended up adding that feature in my own ad-hoc transpiler for datastar expressions a while ago because of how often I found myself needing it, here's a snippet from it in use:
(dx/emit
`(do
(evt.preventDefault)
(?.setPointerCapture el evt.pointerId)
(let [m (or (?.-matrix (.consolidate el.transform.baseVal))
(new js/DOMMatrix))
startx (.-e m)
starty (.-f m)
ctm (.getScreenCTM $svg)
p0 (.matrixTransform (new js/DOMPoint
(.-clientX evt)
(.-clientY evt))
(.inverse ctm))]
;; store offset + toggle dragging
(set! $offdx (- p0.x startx))
(set! $offdy (- p0.y starty))
(set! $isDragging true))))
;; => "(evt.preventDefault(),el?.setPointerCapture(evt.pointerId),(m=>(startx=>(starty=>(ctm=>(inv=>(p0=>($offdx=(p0.x-startx),$offdy=(p0.y-starty),$isDragging=true))(new DOMPoint(evt.clientX,evt.clientY).matrixTransform(inv)))(ctm.inverse()))($svg.getScreenCTM()))(m.f))(m.e))(el.transform.baseVal.consolidate()?.matrix||(new DOMMatrix())))"cool, so (?.fooo) means: call fooo if it exists?
yep, if it's non-null and non-undefined
you were saying something about some-> working differently with undefined or so - can you explain this?
from the compilation output it seems that some-> only checks against the === null case? I don't really know how much undefined crops up in js though / how important it is to guard against it in these checks
it uses two = signs:
> undefined == null
trueahh yes just confirmed that in my browser console 🤦
I think I just copied this macro over from CLJS
hmm and wouldn't it make more sense to use the js/ pseudo-ns for these native escape hatches - ie js/?? rather than js-??
js-... is a CLJS convention for js keywords.
cljs.core/js-arguments
cljs.core/js-comment
cljs.core/js-debugger
cljs.core/js-delete
cljs.core/js-fn?
cljs.core/js-in
cljs.core/js-inline-comment
cljs.core/js-invoke
cljs.core/js-iterable?
cljs.core/js-keys
cljs.core/js-mod
cljs.core/js-obj
cljs.core/js-reserved
cljs.core/js-strbut I have to admit that I don't love js-??
just ?? would be better imo
but then clj-kondo will complain about an unknown var :)
yeah and it would clash with any local or var named _QMARK__QMARK_
yep
js/ is used for objects globally defined
hmm that's true, in my transpiler I just used it as a shorthand for "raw-string passthrough / don't bother with whatever this means"
that would be (js* "...") in CLJS and squint
https://squint-cljs.github.io/squint/?src=KGpzKiAifnt9Py5hIiB7OmEgMX0p
I should get round to releasing that lib some time.. was planning to a couple months ago then discovered some nasty edge cases around let-binding closures / shadowing , decided to dogfood it a bit more first
sounds similar to squint. people have also been using squint for datastar
went with a slightly funky way of transpiling let-bindings into nested lambdas, apparently that's a pretty common technique used by other languages like purescript and browser engines are good at handling it (emit `(let [a 1 b (inc a) c (* a b)] (str a b c))) ;; => "(a=>(b=>(c=>`${a}${b}${c}`)(a*b))(a+1))(1)"
nice. yeah, squint tries to be a little more conservative here it seems:
() => {
const a1 = 1;
const b2 = a1 + 1;
const c3 = a1 * b2;
return squint_core.str(a1, b2, c3);
}
nice idea to inline str as template strings
might be a trick I want to adopt in squint
https://gist.github.com/yuhan0/a87796eedd902996c53f4f17b39067a5 here's an early version of it if you're interested - think I ended up overhauling many of the bits involving macroexpansions
made an issue for str here: https://github.com/squint-cljs/squint/issues/723
the other thing seems to perform somewhat worse than the nested lambda thing so I'm going to leave that
// Version 1
const fn1 = () => (a => (b => (c => `${a}${b}${c}`)(a * b))(a + 1))(1);
// Version 2
const squint_core = { str: (...args) => args.join("") };
const fn2 = () => {
const a1 = 1;
const b2 = a1 + 1;
const c3 = a1 * b2;
return `${a1}${b2}${c3}`;
};
$ node /tmp/foo.js
fn1: 1454.515 ms
fn2: 34.105 ms
const ITERATIONS = 1_000_000_00;with the un-inlined str call, the nested lambda still wins. so inlining that str thing is a good optimization
oof that's good to see an actual benchmark, I think I just found a stackoverflow post or other talking about the technique and took their word for it
I don't think this will cause stackoverflows
*SO the website
Hmm, I thought the inlining of str to templates was an easy no-brainer but for nil values it gets null into the string
I could expand to
`${1 ?? ""}${2 ?? ""}${3 ?? ""}${null ?? ""}${4 ?? ""}`
Maybe this is better:
(core/defmacro stringify [& xs]
`(.join [~@xs] ""))but that's not any faster than what squint already does
or maybe:
(1 ?? "") +
(2 ?? "") +
(null ?? "") +
(undefined ?? "") +
(3 ?? "") +
(4 ?? "")so:
return squint_core.str(1, 2, null, undefined, 3, 4);
return ((1 ?? "") + (2 ?? "") + (null ?? "") + (undefined ?? "") + (3 ?? "") + (4 ?? ""));
hmm, probably the first is good enougheucalypt video: https://clojurians.slack.com/archives/C8NUSGWG6/p1760185407840259