This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-26
Channels
- # babashka (5)
- # beginners (21)
- # cider (26)
- # clojure (46)
- # clojure-art (3)
- # clojure-dev (36)
- # clojure-europe (24)
- # clojure-norway (64)
- # conjure (28)
- # cursive (2)
- # datomic (1)
- # honeysql (12)
- # improve-getting-started (5)
- # introduce-yourself (5)
- # kaocha (1)
- # malli (17)
- # polylith (36)
- # reitit (3)
- # shadow-cljs (8)
I was looking through the old transducer talks, and Rich made a point that the non-transducer arities of functions like map, filter, etc. could essentially be implemented with a call to the transducer arity of the same function. This is not how they ended up implemented in Clojure core, though.
Why is this? Do they incur costs not worth paying when not comp
ed with other transducers?
Probably because it was simpler (and safer) to keep the existing implementation, rather than rewrite all of that code?
Fair enough; safer. Simpler, is it really? Slightly different code in two places executing something that hopefully ends up being the same in the end. Surely it would be simpler to just issue the call to the other arity?
Sorry, I meant the process would be simpler: of only adding the new arity and not touching the existing code.
Transducers are inherently greedy. And the core functions are mostly inherently lazy. I would expect some difficulty making those congruent. No?
(map f coll)
= (sequence (map f) coll)
is lazy.
And you can do some funky things with eduction
to make it super-lazy (and skip chunking)
I think that’s one of the really neat things about transducers: they can be lazy or greedy as required, given the context they’re used in.
eduction
produces a reducible, not a lazy sequence, but yeah eduction
is an interesting one...
It's probably worth noting that the chunking behavior of sequence
does seem to match the core lazy functions tho'...
(defn print-inc
[n]
(println "inc" n)
(inc n))
(defn print-even?
[n]
(println "even?" n)
(even? n))
(->> (range 1000)
(map print-inc)
(filter print-even?)
(take 2))
;; 64 printlns
(sequence
(eduction
(map print-inc)
(filter print-even?)
(take 2)
(range 1000)))
;; 8 printlns
sequence constructs an iterator then builds chunking and a seq on top of the iterator
(take 2 (filter #(do (println "n" %) (even? %)) (range 100)))
prints up to n 31
but
(take 2 (sequence (filter #(do (println "n" %) (even? %))) (range 100)))
prints up to n 64
@U06B8J0AJ In addition to Sean's first answer, this ^ likely also contributed to the decision not to reimplement. Some potential for breakage of code built around old chunking size.
Well, if you skip eduction, the behaviour is identical. Edit: no, I seem to be wrong.
(sequence (comp
(map print-inc)
(filter print-even?)
(take 2))
(range 1000))
8 printlns(transduce (take 2) conj [] (eduction (filter #(do (println "n" %) (even? %))) (range 100)))
only prints up to n 2
Code shouldn't rely on chunking size -- it's an implementation detail 🙂
Things get weird combing a chunked seq like from a vector with sequence, because the vector seq and the iterator seq from sequence could have differing ideas about chunks
Core Team won't guarantee the implementation details, but they definitely still seem to weigh such breakage potential as a meaningful factor regardless.
Yeah, possibly. So we have two theories: • Minimum amount of update to the codebase needed, and • Keep some implementation details from changing
Those aren't conflicting theories, if that's not clear. I would bet the first one held more weight.
No, could be either or both, or some unknown third case. “Couldn’t be arsed”, “the existing code has a special place in my heart”, etc.
Or perhaps, performance characteristics that would change slightly, but unnecessarily.
I wonder if Clojure would have looked slightly different if protocols and transducers had been there from the beginning.
(def. about protocols -- but I think also about transducers too)
Aha! How uncharacteristic. I know from the anniversary chat that he doesn’t subscribe to the many-worlds theory 🙂
Well, the implementation would be different. The language would be largely identical I suspect.
There would probably be less Java and more Clojure if protocols were there from the start.
Even if the language wouldn’t change, it might have had an impact on codebases. We might have seen more extend
s around. And, I guess, fewer ->>
s if transducers were imprinted on users early on.
I'm starting a project with this deps.edn
file:
{:deps {io.github.nextjournal/clerk {:mvn/version "0.11.603"}
link.szabo.mauricio/spock {:mvn/version "0.1.1-SNAPSHOT"}}
:paths ["resources/jpl.jar"]
:aliases {:prolog {:jvm-opts ["-Djava.library.path=resources"]}}}
and when I fire up the repl I get the warning:
WARNING: Use of :paths external to the project has been deprecated, please remove: resources/jpl.jar
how could I solve it while maintaining the resources/jpl.jar
file?Do you want to simply add that jar to the classpath? If so, you can use :local/root
within :deps
: https://clojure.org/guides/deps_and_cli#local_jar
Awesome suggestion as usual @U2FRKM4TW! This is what worked in the end:
{:paths ["src"]
:deps {io.github.nextjournal/clerk {:mvn/version "0.11.603"}
link.szabo.mauricio/spock {:mvn/version "0.1.1-SNAPSHOT"}
jpl/jpl {:local/root "./resources/jpl.jar"}}
:aliases {:prolog {:jvm-opts ["-Djava.library.path=./resources"]}}}
also pinging @U3Y18N0UC as this is probably worth of inclusion in the spock
readme