Random interesting clojure observation of the day:
It's always been a little bit of an annoyance that you can't use top-level data literals in #(). E.g. #({:foo %}) is invalid, you have to use #(hash-map :foo 1), #(vector % :b), etc. Consider this alternative next time:
#(-> {:foo %})
It's more concise than identity, as->, or let, and to me reads more clearly than hash-map or vectorSure, I'd put that in the same boat as let and identity.
Cognitive load is different, IMO. No tricks, no extra syntax, the most direct expression one could come up with.
I personally feel like if I can get used to (seq x) meaning "not-empty", I can get used to #(-> {:foo %}), but we can disagree
Oh, I definitely agree.
It's just that (seq x) is a documented idiom and the -> trick is not. :)
And even the fact that it's an idiom seems to be shaky nowadays, with empty? getting special treatment.
I had to try that in a REPL to convince myself it even worked...
I expect a linter would flag it tho'?
(-> x) is basically a shorter version of identity, which makes perfect sense if you think about it
clj-kondo doesn't flag it, by default at least
to me i didn’t “get used to” seq x but i understood it. This to me is a workaround around the shorthand that makes it longer than what the shorthand replaces. So to me its more confusing and more verbose than what you can just natively do
I think we have pretty much exactly this discussion once a year or so. :)
yeah. i think you are correct.
I prefer #(hash-map :foo 1) over code golfing ways to avoid it, but cool
#(do {:foo %})
I have used #(-> {:foo %1}) in the REPL, but not much in application code. I like how concise it is, and it doesn't feel very "tricky" to me, but it does take a little more thinking than (fn [v] {:foo v}), which is also quite short.
assoc should be a contender here as well - in terms of readability / intent #(assoc {} :foo %) uses everything in idiomatic ways. also (def id identity) is straightforward
There's also a weird edge-case with map/set data literals to be aware of:
user=> (#(hash-map :foo 1, % 2) :foo)
{:foo 2}
user=> (#(-> {:foo 1, % 2}) :foo)
java.lang.IllegalArgumentException: Duplicate key: :foo [at <repl>:1:2]
Which makes me wonder if (despite appearances) it might actually be less efficient than simply calling the constructor? Having to perform integrity checks at runtimeoh also if we're really looking to get spicy i'd like to nominate
#((completing slurp) {:foo %})
Doesn’t {:a 1} emit a constant, vs. (assoc {} :a 1) and the equivalent hash-map? Probably makes no difference in the vast majority of cases, but if you’re in a very hot loop: maybe?
I think the literals have a slight edge even when the value is dynamic IIRC.
When keys are guaranteed to be unique, literals should be the fastest.
If they are unique in the data but it cannot be guaranteed at read time, both literals and constructor functions should have the same performance.
assoc is the slowest, but (assoc nil k v) specifically is as fast as it gets when it comes to values that aren't known at read time.
None of this means anything for tiny structures and lack of profiling - I just took a glance at the impl and the result of clj-java-decompiler.
So I made this strange https://github.com/johnmn3/perc#mapping-and-reducing thing a while back that has that as what I call "return literals," where you can do #%{:a {:b like:
(->> [{:a {:z {:x 5}}
:b {:c {:p {:q 6}}}}
{:a {:z {:x 7}}
:b {:c {:p {:q 8}}}}
{:a {:z {:x 9}}
:b {:c {:p {:q 10}}}}]
(mapv #%{:a {:z {:x (inc %:a:z:x)}}
:b {:c {:p {:q (dec %:b:c:p:q)}}}})
(reduce #%{:a {:z {:x (+ %1:a:z:x %2:a:z:x)}}
:b {:c {:p {:q (+ %1:b:c:p:q %2:b:c:p:q)}}}}))
I never ended up needing to reach for perc very much. But with AI coding I might experiment with using it to save on tokens.Oh, but what if it let you condense the map construction as well? Like:
(mapv #%{:a:z:x (inc %:a:z:x)
:b:c:p:q (dec %:b:c:p:q)})
Agents could write some pretty terse transformations with "path expressions," I call them.Also can be written as:
(mapv #%{:a:z:x %:a:z:x%inc
:b:c:p:q %:b:c:p:q%dec})Design question.
I have a library that adds async/await to Clojure. Currently, error is any Throwable.
(async (ex-info "" {})) -> Is considered a failed task, because it returned a Throwable
(async (throw (ex-info "" {}))) -> Is considered a failed task, because it threw a Throwable
Both returning or throwing a Throwable fails the async task. When you await it, it will throw back the Throwable in either case. You can use await* to have the error returned instead of thrown, and in both cases await* will return the error.
I'm now adding async generators