This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-20
Channels
- # aleph (78)
- # announcements (13)
- # architecture (8)
- # aws (3)
- # babashka (110)
- # beginners (38)
- # calva (2)
- # clerk (1)
- # clojure (118)
- # clojure-austin (3)
- # clojure-dev (8)
- # clojure-europe (50)
- # clojure-france (2)
- # clojure-nl (1)
- # clojure-portugal (1)
- # clojure-uk (3)
- # clojurescript (101)
- # clr (10)
- # data-science (15)
- # datascript (5)
- # datomic (3)
- # events (1)
- # fulcro (22)
- # graalvm (2)
- # gratitude (3)
- # guix (1)
- # honeysql (1)
- # hyperfiddle (72)
- # jobs (3)
- # lsp (23)
- # malli (18)
- # membrane (29)
- # obb (1)
- # releases (1)
- # ring-swagger (2)
- # shadow-cljs (8)
- # squint (113)
- # xtdb (9)
I noticed that doseq
emits things like _nth
, unchecked_inc
, chunk_first
and chunk_rest
. I don't see imports for those though. How does that work?
@lilactown doseq might work in cherry but not in squint, similar to for
, I think it might be better to disable it in squint until a good replacement is implemented
Hmm, it seems to work:
borkdude@m1 /tmp/dude $ npx cherry run script.cljs
[cherry] Running script.cljs
1
2
3
borkdude@m1 /tmp/dude $ npx squint run script.cljs
[squint] Running script.cljs
1
2
3
I started with wanting to play with implementing for
. maybe I should start with doseq
just so I understand the project dependencies: if I'm editing squint/src/squint/internal/macros.cljc
, I don't need to worry about cherry compat right?
I tried a few different approaches with git submodules, git history rewriting, etc, etc but I ended up with this to reduce the amount of hassle
not only that but I wanted to have the common part in its own repo but also a way to run tests against that. then I tried having everything in one repo but then with history rewriting so cherry and squint could keep their own repositories (with their own issues, stars, etc)
but I think when tests are run against squint when the common compiler code changes, it's sufficient for now
I think it would be really nice if doseq
emitted something like for (let x of seq) { ,,, }
I think I could use a lot of the same code for for
, but I need to construct a lazy iterable. do you think we could export the constructor for that?
Yeah, maybe we could do it like __LazyIterable
to indicate that it's a constructor only we can use
I made a comment in the PR about the usage of seq
: note that this currently doesn't work correctly for (range .. ..)
like things: seq never returns nil
I think
Maybe we should go with some memoized sequence type that memorizes the realized elements like in Clojure...
We could maybe look at this: https://github.com/mlanza/atomic
it literally emits
for (let ~{} of ~{}) { ~{} }
which works with all of our LazyIterable
s since for ... of
calls the Symbol.iterator
function under the hoodI'm already in a rabbit hole of implementing IteratorSeq from CLJS which is basically the immutable layer we could use to make seq
and next
work maybe, while also preventing multiple side effects when walking the seq twice. I realize it's now unrelated to your PR, good to know.
class IteratorSeq {
constructor(value, iter) {
this.iter = iter;
this.value = value;
this.rest = null;
}
[IIterable] = true;
[Symbol.iterator]() {
yield value;
yield iteratorSeq(iter);
}
}
function iteratorSeq(iter) {
let v = iter.next();
if (v.done) {
return null;
} else {
return new IteratorSeq(v.value, iter);
}
}
no, but seq
returns nil
when there's no more elements and we can't decide that without realizing the first element once for the nil test and then later again when someone really wants the element
when doseq
is compiled, :let
compiles to just let x = y
. but similar code for for
compiles to (function () { let x = y })()
this is a problem because I'm trying to yield
inside of that inner function, which doesn't work
This happens because you're in an expression context rather than a statement context I think
ok, watch this:
$ ./node_cli.js -e "(def xs [1 2 3]) (def ys (map prn xs)) (vec ys) (vec ys)"
1
2
3
1
2
3
$ ./node_cli.js -e "(def xs [1 2 3]) (def ys (iterator-seq (map prn xs))) (vec ys) (vec ys)"
1
2
3
I pushed this to https://github.com/squint-cljs/squint/tree/iterator-seq - something to consider...
how does the compiler know when it's in an expression context? can I manually twiddle that inside of the for
macro impl?
this is a problem with js*
, there's no way to currently tweak that, but maybe we can make that work somehow
so, it depends at the point in which you are in your CLJS expression where you call js*
does that mean that if I had a special form that emitted for ... of
I could control the context ?
what I mean is this:
$ ./node_cli.js --show -e "(if true (js* \"~{}\" (let [x 2])))"
if (true) {
let x1 = 2;
null};
vs:
$ ./node_cli.js --show -e "(let [x (if true (js* \"~{}\" (let [x 2])))])"
let x1 = (true) ? ((function () {
let x2 = 2;
return null;
})()) : (null);
null;
and expressions compiled by js*
are emitted as expressions, rather than top level statements
here's the CLJS
(for [x (range 3)
y (range 3)
:let [z (+ x y)]]
z)
and the output
import { lazy, range } from 'squint-cljs/core.js'
lazy(function* () {
for (let x of range(3)) {
for (let y of range(3)) {
(function () {
let z1 = (x + y);
yield return z1;
})()
}
}
});
the proper output should be
import { lazy, range } from 'squint-cljs/core.js'
lazy(function* () {
for (let x of range(3)) {
for (let y of range(3)) {
let z1 = (x + y);
yield z1;
}
}
});
that does look like a similar problem: the js* env is inside an expr context which is why it's emitting a self-calling function
but maybe you should be able to "reset" the context in which js* starts, e.g. via metadata or so
There is a bit of overhead of doing this cached thing:
$ ./node_cli.js -e "(def xs (range 1000)) (def ys (seq (map identity xs))) (time [(count (vec ys)) (count (vec ys))])"
"Elapsed time: 0.388041 msecs"
$ ./node_cli.js -e "(def xs (range 1000)) (def ys (es6-iterator-seq (map identity xs))) (time [(count (vec ys)) (count (vec ys))])"
"Elapsed time: 30.482041 msecs"
I think this is one of the reasons that CLJS implements chunking for these cached sequencesyeah it's basically a linked list. you have to chase the pointer of the tail all the time
idk, this stuff is hard. maybe Rich knew what he was doing when he implemented all of this ๐
yeah, I think optimally we'd move to cljs semantics, i.e. things are cached (side effects only once), but in an efficient way (chunks) and we have a Seq + Seqable protocol
the chunking happens only if the underlying seq is also chunked, else it's pretty much what I have in the iterator-seq
branch
I exposed that caching thing as es6-iterable-seq
- we can use that as a mechanism for caching, if people find that important, and keep the rest as is, for now. And maybe indeed, let seq
return that automatically...
but what about first
, rest
etc. often you need both first
and rest
and currently you will do the calculation of the first element twice...
what I'm learning here is fundamentally you need to embrace the mutability with iterators, or you need to do caching with immutability
๐งต for the core.js/core.edn issue I opened https://github.com/squint-cljs/squint/issues/294
it seems like it would be nice if I could make changes locally to core.js
and everything would reference it inside of the squint project