This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-06
Channels
- # adventofcode (54)
- # announcements (3)
- # babashka (34)
- # beginners (38)
- # calva (27)
- # cherry (5)
- # clj-kondo (34)
- # clojure (26)
- # clojure-bay-area (4)
- # clojure-berlin (3)
- # clojure-europe (26)
- # clojure-india (6)
- # clojure-nl (1)
- # clojure-norway (54)
- # clojure-uk (2)
- # conjure (3)
- # cursive (4)
- # data-science (1)
- # emacs (6)
- # events (4)
- # fulcro (2)
- # hugsql (6)
- # hyperfiddle (38)
- # lsp (3)
- # matrix (1)
- # membrane (5)
- # off-topic (27)
- # re-frame (3)
- # releases (1)
- # sci (8)
- # shadow-cljs (34)
- # squint (132)
clojure.set
is not supported, yet, I take it?
TypeError: Failed to resolve module specifier 'clojure.set'
I have written intersection like this:
(defn intersection [& xs]
(reduce (fn [acc s]
(set (filter #(.has acc %) s))) xs))
for cherry:
(defn intersection [& xs]
(reduce (fn [acc s]
(set (filter #(contains? acc %) s))) xs))
I see. The function works in Cherry if I call it like so:
(intersection (set [1 2 3]) (set [1 2]))
ah yes, in general you always need to call set functions in clojure.set with sets, not other collections
have you tried the smaller parts? when I evaluate:
(parse-card 4 "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53")
it crashes with:
Error: function (_PERCENT_1) {
return globalThis.user.str.split.call(null, _PERCENT_1, / +/)
} is not ISeqable
I’m also confusing myself testing in both Cherry and Squint. I’ll try to stay with Cherry testing now first.
in squint, collections are just native JS collections and they do not work as functions in call position
Thanks. I think the editor must have nuked the hash in front of (set ...
there. No idea when that happened.
I discovered a few edge cases in squint, e.g. (reduce + [])
returns undefined instead of 0
As for reductions
. Is this fine, or is the unDRYness horrible?
export function reductions(f, arg1, arg2) {
f = toFn(f);
let coll, val;
if (arg2 === undefined) {
// (reduce f coll)
const iter = iterable(arg1)[Symbol.iterator]();
const vd = iter.next();
if (vd.done) {
val = f();
} else {
val = vd.value;
}
coll = iter;
} else {
// (reduce f val coll)
val = arg1;
coll = iterable(arg2);
}
if (val instanceof Reduced) {
return val.value;
}
const vals = [];
for (const x of coll) {
val = f(val, x);
if (val instanceof Reduced) {
val = val.value;
vals.push(val);
break;
} else {
vals.push(val);
}
}
return vals;
}
Trying to port reductions from clojure.core I get this:
export function reductions(f, arg1, arg2) {
f = toFn(f);
let ret;
if (arg2 === undefined) {
// (reductions f coll)
if (arg1.length > 0) {
ret = reductions(f, first(arg1), rest(arg1));
} else {
ret = list(f());
}
return new LazySeq(ret);
} else {
// (reductions f val coll)
if (arg1 instanceof Reduced) {
return list(arg1.value);
}
if (arg2.length > 0) {
ret = reductions(f, first(arg2), rest(arg2));
} else {
ret = list(f());
}
return cons(arg1, new LazySeq(ret));
}
}
But neither cons, nor LazySeq is printed nicely enough in the playground:
(reductions + [1 2 3])
=>
LazySeq {
f: Cons { x: 1, coll: LazySeq { f: [List], res: undefined } },
res: undefined
}
(reductions + 7 [1 2 3])
=>
Cons {
x: 7,
coll: LazySeq { f: Cons { x: 1, coll: [LazySeq] }, res: undefined }
}
Basically I don’t know if I am even close. 😃OK, so I went closer to clojure.core’s implementation and now have this:
export function reductions(f, arg1, arg2) {
f = toFn(f);
if (arg2 === undefined) {
// (reductions f coll)
const s = seq(arg1);
return lazy(function* () {
for (const x of iterable(s ? reductions(f, first(s), rest(s)) : list(f()))) {
yield x;
}
});
} else {
// (reductions f val coll)
if (arg1 instanceof Reduced) {
return list(arg1.value);
}
const s = seq(arg2);
return cons(arg1, lazy(function* () {
let ret = s ? reductions(f, f(arg1, first(s), rest(s))) : null;
for (const x of iterable(ret)) {
yield x;
}
}));
}
}
Which is maybe a bit closer, but not quite right yet:
reductions + [1 2 3])
=>
LazyIterable(1) [
[
List(1) [ 1 ],
List(1) [ '3' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
List(1) [ '0' ],
'...'
]
]
I’ll chill a bit with this until you have told me if it looks like I should keep trying. 😃There are no tests for reductions. So I will have to write them first to tell you. I am guessing that for the non-DRY version the tests would pass. But for the more sensible implementation no tests would pass right now.
I've looked at the clojurescript source for some things but I feel like this must not be much different than the jvm version https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L10278-L10292
yeah, scratch that, they're identical
I don’t know why people find parens in Clojure tricky. What is truly tricky is parens in JavaScipt… Anyway, this works:
function* _reductions(f, arg1, arg2) {
console.log('reductions', arg1, arg2);
const init = arg2 === undefined ? undefined : arg1;
const coll = arg2 === undefined ? arg1 : arg2;
if (init === undefined) {
// (reductions f coll)
const s = seq(coll);
yield* lazy(function* () {
const ret = s ? _reductions(f, first(s), rest(s)) : list(f());
for (const x of iterable(ret)) {
yield x;
}
});
} else {
// (reductions f val coll)
if (init instanceof Reduced) {
return list(init.value);
}
const s = seq(coll);
yield* cons(init, lazy(function* () {
if (s) {
for (const x of iterable(_reductions(f, f(init, first(s)), rest(s)))) {
yield x;
}
}
}));
}
}
export function reductions(f, arg1, arg2) {
f = toFn(f);
return _reductions(f, arg1, arg2);
}
Where “works” so far means:
(reductions + []) => Object [Generator] {}
(vec (reductions + [])) => [ 0 ]
(vec (reductions + [1 1 1 1])) => [ 1, 2, 3, 4 ]
(vec (reductions get-counts '() counts)) ; from my day 4, part 2
=>
[
List(0) [],
List(1) [ 1 ],
List(2) [ 1, 1 ],
List(3) [ 2, 1, 1 ],
List(4) [ 4, 2, 1, 1 ],
List(5) [ 7, 4, 2, 1, 1 ],
List(6) [ 15, 7, 4, 2, 1, 1 ]
]
I don't think they find parens tricky as much as in an unfamiliar position, and what really throws them off is how many keywords are missing in a way that's unique to lisps. things like while/then/end/fi/else/etc aren't there and that's what they usually use as landmarks to grok code -- instead in lisps everything is a parens so it it's a lot harder to parse at first
I know that was a throwaway joke on your part but it's something i've been thinking about 😅
Damn. This test fails:
(is (eq [0 1 3 3] (jsv! '(vec (reductions #(if (< %2 3)
(+ %1 %2)
(reduced %1))
(range 5)))))
"reduced early")
The last 3
is missing…
FAIL in (reductions-test) (squint/compiler_test.cljs:953:9)
no val
reduced early
expected: (eq [0 1 3 3] (jsv! (quote (vec (reductions (fn* [p1__73613# p2__73612#] (if (< p2__73612# 3) (+ p1__73613# p2__73612#) (reduced p1__73613#))) (range 5))))))
actual: (not (eq [0 1 3 3] #js [0 1 3]))
If I clean it up a bit, it works and passes the test that my version fails with:
export const reductions = (function () {
let f19 = (function (var_args) {
let G__221 = arguments.length;
switch (G__221) {case 2:
return f19.cljs$core$IFn$_invoke$arity$2((arguments[0]), (arguments[1]))
break;
case 3:
return f19.cljs$core$IFn$_invoke$arity$3((arguments[0]), (arguments[1]), (arguments[2]))
break;
default:
throw new Error(str("Invalid arity: ", alength(arguments)))}
});
f19.cljs$core$IFn$_invoke$arity$2 = (function (f, coll) {
return new LazySeq((function () {
let temp__27058__auto__3 = seq(coll);
if (truth_(temp__27058__auto__3)) {
let s4 = temp__27058__auto__3;
return reductions(f, first(s4), rest(s4))} else {
return list(f())}
}))
});
f19.cljs$core$IFn$_invoke$arity$3 = (function (f, init, coll) {
if (truth_(reduced_QMARK_(init))) {
return list(deref(init))} else {
return cons(init, new LazySeq((function () {
let temp__27136__auto__5 = seq(coll);
if (truth_(temp__27136__auto__5)) {
let s6 = temp__27136__auto__5;
return reductions(f, f(init, first(s6)), rest(s6))}
})))}
});
f19.cljs$lang$maxFixedArity = 3;
return f19
})();
If I change my hand-ported one to return LazySeq instead of being a generator, then it passes my current tests:
function _reductions(f, arg1, arg2) {
const init = arg2 === undefined ? undefined : arg1;
const coll = arg2 === undefined ? arg1 : arg2;
if (init === undefined) {
// (reductions f coll)
const s = seq(coll);
return new LazySeq(function () {
return s ? _reductions(f, first(s), rest(s)) : list(f());
});
} else {
// (reductions f val coll)
if (reduced_QMARK_(init)) {
return list(init.value);
}
const s = seq(coll);
return cons(init, new LazySeq(function () {
if (s) {
return _reductions(f, f(init, first(s)), rest(s));
}
}));
}
}
Why that makes a difference for the reduced
test is beyond me, but if we are good with returning this, I am happy.I made it a tad more readable like so:
function _reductions(f, arg1, arg2) {
const [init, coll] = arg2 === undefined ? [undefined, arg1] : [arg1, arg2];
const s = seq(coll);
if (init === undefined) {
// (reductions f coll)
return new LazySeq(function () {
return s ? _reductions(f, first(s), rest(s)) : list(f());
});
} else {
// (reductions f val coll)
return reduced_QMARK_(init)
? list(init.value)
: cons(init, new LazySeq(function () {
if (s) {
return _reductions(f, f(init, first(s)), rest(s));
}
})
);
}
}
Now more tests. Expecting to not find one that fails. 😃and it would also be easier to debug by inserting some console.log in between stuff, vs when you have just one return + expr
Is there a better way to express this?
(is (= "[object Object]" (str (jsv! '(reductions + '()))))
"Returns LazySeq")
Does it make sense to make almost all tests in the style (eq (reductions + '()) (jsv! '(vec (reductions + '()))
?
this can be solved using a macro:
(defmacro eq-expr [expr]
`(eq expr (jsv! (list 'quote expr)))
A bit unlucky with the macro here:
FAIL in (reductions-test) (squint/compiler_test.cljs:953:9)
no val
expected: (eq-expr (vec (reductions + (quote ()))))
actual: #object[TypeError TypeError: Cannot read properties of undefined (reading 'cljs$core$IFn$_invoke$arity$1')]
Looks like it could do wonders with the compiler tests. 😃 But I am not keen on diving into another rabbit hole. Not explored the current one yet.
Hi all. Newie question. Why doesn’t something like this work? I presume bc of the macro?
(squint/compile-string "(for [i (range 10)] (println i))")
; Execution error (ArityException) at squint.compiler/eval8630$fn (compiler.cljc:168).
; Wrong number of args (0) passed to: squint.compiler/transpile-form/fn--8703
@U068RUGD875 In which environment is this?
@U068RUGD875 Fixed on main
what service!
Will do, thanks. I was surprised that was a bug. just thought I wasn’t using it right.
CLJS is more lenient in functions with fixed arguments which is where the bug came from
What I was playing around with is compiling and serving the squint code from a clojure process that’s acting as a web app backend.
saw that, yep
ok, another newbie question. I have a file (client_macros.cljc) with a simple macro in it and
(ns client-macros)
(defmacro add [x y]
`(+ ,x, ,y))
and another file client_js.cljs that calls it
(ns client-js
(:require-macros [client.macros :refer [add]]))
(println (add 1 2))
when I compile with compile-string*, the call to add isn’t expanded. I also made a squint.edn per the readme
{:paths ["src-squint"]}
Am I supposed to do something else?hmm, this isn't yet supported in the JVM side of things, only in the node side of things and when you work via the CLI: npx squint compile
but there is a way to configure macros another way, let me look that up
I think this should work in squint as well: https://github.com/nextjournal/clerk/blob/ff2bf8b0b5d596286fb1d22408b14456fee2dbf7/src/nextjournal/clerk/cherry_env.cljs#L52C1-L73
user=> (s/compile-string "(foo.bar/baz 1)" {:macros {'foo.bar {'baz (fn [&form &env x] (inc x))}}})
"import * as squint_core from 'squint-cljs/core.js';\n2;\n"
thank you, I’ll give this a try
if you have a JVM macro, you can do this:
(s/compile-string "(foo.bar/baz 1)" {:macros {'foo.bar {'baz #'jvm-namespace/the-macro}}})