Fork me on GitHub
#beginners
<
2022-12-18
>
mister_m01:12:27

Is there a conj alternative for seqs and vectors? I am finding conj doing different things based on the argument type, combined with seq-producing functions that work with vectors to be very confusing and I’d rather use something explicit in place of conj for a seq and a vector

skylize03:12:54

cons will always add to the front at the expense of performance for vectors. Also turns your vector into a seq, whereas conj will preserve the vector. There is no built-in for attaching to the back of a seq.

phill18:12:08

conj "adds" to the more efficient end of the structure. You should use the appropriate data structure, not add to the wrong end of the wrong structure. P.S. If you want to work with vectors using map and filter and such, it's your choice whether to use the sequence-returning arity of map or filter and wrap it with (vec ...) to pour the result into a vector, or use the transducer arity that skips making the sequence.

☝️ 1
heyarne10:12:55

I'm trying to generate a path of points, it has a maximum length but the exact length is not known upfront. What's the idiomatic way to do this? I'm using reduce like this at the moment, but it feels weird to generate a range and just ignore it:

(reduce (fn [[head & _ :as path] _]
                (if-let [next (next-node quadtree head)]
                  (conj path next)
                  (reduced path)))
        (list seed)
        (range max-path-length))

Miķelis Vindavs11:12:59

you could try expressing the “unfolding” using either iterate or lazy-seq and then limit it using take

Miķelis Vindavs11:12:51

(->> (list seed)
     (iterate (fn [[head :as path]] 
                (conj path (next-node quadtree head))))
     (take max-path-length))

heyarne11:12:58

i've had a similar idea, so maybe it's good 🙂 what bugged me about it is that iterate returns '([p1], [p1 p2], [p1 p2 p3], [p1 ... pn]) and i only need the last iteration. it seemed wasteful someow to generate all of these intermediate sequences and then just ignoring them

Miķelis Vindavs11:12:23

Oh, got it. Well you can just call nth on it. Of course it will be slightly slower than reduce

heyarne11:12:57

ooooh that's nifty!

skylize17:12:03

> it feels weird to generate a range and just ignore it: Using a range as a counter for recursion or iteration is pretty common in the wild. But I agree it seems kind of smelly here. > it seemed wasteful someow to generate all of these intermediate sequences and then just ignoring them No problem there. Clojure's immutable data structures benefit from structural sharing. So the vectors you create at the beginning are included as part of later vectors, rather than being discarded for potential garbage collection. conj does not need to iterate to attach a new value. So the (cheap) cost of growing the collection is more-or-less the same on each iteration. ---- Destructuring an ordered collection only extracts as many elements as you specify bindings for. You do not need to explicitly ignore the tail.

(let [[head :as foo] [:baz :biz :buz]]
  {:head head :foo foo})

{:foo [:baz :biz :buz], :head :baz}

yogthos21:12:27

wrote a bit about my approach to structuring Clojure apps in a way that keeps side effects isolated and produces reusable components https://yogthos.net/posts/2022-12-18-StructuringClojureApplications.html

👍 3
James Pratt23:12:03

I think @stuarthalloway has missed what the bug actually was in his example of debugging a simple problem here: https://youtu.be/FihU5JxmnBg?t=1310

James Pratt23:12:19

https://stackoverflow.com/questions/32929149/why-this-partial-not-working/74844940 was why does this code results in an exception:

(def partial-join (partial (clojure.string/join ",")))
(partial-join ["foo" "bar"])
;; => ClassCastException java.lang.String cannot be cast to clojure.lang.IFn .repl/eval12557 (form-init2162333644921704923.clj:1)

James Pratt23:12:43

Stuart jumped to the idea that it was because (clojure.string/join ",") returns ",". But (clojure.string/join ",") should not be being called there at all but instead the function and first parameter should be passed to partial as so (notice the lack of parens round clojure.string/join):

(def partial-join (partial clojure.string/join ","))
  (partial-join ["foo" "bar"])
  ;; => "foo,bar"

skylize23:12:42

He failed to clarify the next step of jumping from "`(partial ",")` doesn't make any sense" to the actual solution of removing parens. But I can't say I believe for a second that he "missed the bug". Finding a bug is different from fixing it, and he only describes a heuristic method for finding the bug.

James Pratt00:12:41

He mentions the same bug again later in the talk here: https://youtu.be/FihU5JxmnBg?t=2468

James Pratt00:12:16

Again he says that the bug is caused by the fact that (clojure.string/join ",") with arity 1 joins the items in the first parameter which you could have spotted if you read the documentation of the function. But that is not the problem.

Bob B01:12:40

The question was "why does this code throw an exception?". I'd say providing different code that doesn't throw an exception doesn't answer that question. Evaluating the forms as demonstrated does explain the exception; the join returns ",", and calling partial on "," tries to use a String as an IFn, which leads to the ClassCastException.