This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-05
Channels
- # aleph (1)
- # announcements (1)
- # babashka (6)
- # beginners (25)
- # biff (6)
- # calva (61)
- # clojure (15)
- # clojure-europe (2)
- # clojurescript (34)
- # cursive (7)
- # events (2)
- # figwheel-main (5)
- # fulcro (10)
- # gratitude (1)
- # malli (2)
- # pathom (1)
- # polylith (3)
- # portal (1)
- # re-frame (3)
- # reagent (10)
- # releases (2)
- # shadow-cljs (19)
- # spacemacs (5)
- # xtdb (2)
Hi - On p216 of The Joy of Clojure we find this code:
(defrecord TreeNode [val l r]
FIXO
(fixo-push [t v]
(if (< v val)
(TreeNode. val (fixo-push l v) r)
(TreeNode. val l (fixo-push r v))))
(fixo-peek [t]
(if l
(fixo-peek l)
val))
(fixo-pop [t]
(if l
(TreeNode. val (fixo-pop l) r)
r)))
(def sample-tree2 (reduce fixo-push (TreeNode. 3 nil nil) [5 2 4 6]))
This throws an exception. I have fixed it by adding
(extend-protocol FIXO
nil
(fixo-push [t v] (TreeNode. v nil nil)))
Is this the right way to go?Somewhat confusingly, that is explained earlier on page 212: You can even extend a protocol to nil. You’d be forgiven for not immediately seeing why you’d want to do this, but consider how TreeNode implements fixo-push; and yet the sample-tree you’re using was built using xconj instead. Trying to build up a tree the same way with fixo-push runs into a problem:
(reduce fixo-push nil [3 5 2 4 6 0])
; java.lang.IllegalArgumentException:
; No implementation of method: :fixo-push
; of protocol: #'user/FIXO found for class: nil
The xconj implementation specifically handled the initial nil case, but because protocol methods dispatch on the first argument, you need special support from extend
to get fixo-push to behave similarly. This is done by extending a protocol to the value
nil, like this:
(extend-type nil
FIXO
(fixo-push [t v]
(TreeNode. v nil nil)))
The first edition of JoC was less confusing in the order it explained this. They moved stuff around in the second edition and this was a chapter that ended up worse for it.Thank you, Sean. I was reading in order, but I forgot that passage by the time I tried out the code. I must have dredged the solution up from memory. Damn, I thought I was getting to be creative!
We must have different editions or something , the treenode stuff is not on 216 in mine
First Edition has it on page 195, Second Edition, it's on page 216.
195, which is a few pages after the treenode defrecord is introduced, extends the protocol to nil
I'm running a basic pedestal server on my local dev machine. Looking at the browser dev tools network tab, I see it takes 100ms to download ("content download" in Chrome, "receiving" in Firefox network timing) a 7MB js file, while it takes 300ms to download a 300kB transit API response. I've separately timed how long it takes to generate that transit, it's not that. I'm completely clueless about this, I don't know how to approach thinking about it. Does this strike you as strange? Worth thinking about?
There are some known issues related to transit stream flushing that you could be seeing
Is this transit-clj on server side?
This issue is the one I'm thinking about https://github.com/cognitect/transit-clj/issues/46
It is transit-clj. I'm letting https://github.com/pedestal/pedestal/blob/50fe5ea89108998ac5a79a02a44432fd111ea6f8/service/src/io/pedestal/http.clj#L129 handle the translation to transit. I don't think it is streaming?
Recording performance while receiving the data shows it is coming in chunks of on the order of 10 Bytes. But I have no idea if that is standard behaviour.
Ah, I see Transfer-Encoding: chunked
. It would seem the linked issue is indeed relevant.
Yeah, I think you're probably getting a large number of tiny packets
(defn write-transit [data]
(let [baos (java.io.ByteArrayOutputStream.)]
(transit/write (transit/writer baos :json) data)
(.toString baos "utf-8")))
Using this instead of the interceptor reduces the size and time (time by a factor of 100). (Found it from @U04V15CAJ somewhere, he mentioned this strategy in issue #43 linked in the issue above.)
Thanks for your help!This is on our radar to work on, just haven't gotten a chance yet
Is there a better way to express returning the first value in a collection that returns true given some function? maybe just filtering and taking the first is better?
example of what i currently have (some #(when (not (str/blank? %)) %) ["yolo" ""])
I'd prefer filtering and taking the first.
I've seen (def ffilter (comp first filter))
in multiple codebases.
Not very satisfying TBH.
yea. I mean, this usually means something is wrong with the modeling upstream. But generally not worth fixing.
Somewhat related thread: https://clojurians.slack.com/archives/C053AK3F9/p1634945957067400
In this case, negating might make it clearer:
(first (remove str/blank? ["yolo" ""]))
reads as "give me the first non-blank string"Do the filter/some solutions cause iteration across the entire input collection? If so - what about something that terminates early?
(defn get-when [xs f] (if (f (first xs)) (first xs) (if (<= (count xs) 1) nil (recur (rest xs) f))))
Testing simple samples or lazy seqs with range/filter application, it'd seem such a thing doesn't matter, but if you make a predicate that intentionally takes time (toss in a Thread/sleep into it) - you can observe the other solutions will take total time of at a minimum, collection length * predicate time, while early termination worst case will take that long (if the final element was the only one to satisfy the check) - and in all other cases, be better - but, probably a bit of a micro optimization unless your data set is big and your predicate is slow 🙂
> Do the filter/some solutions cause iteration across the entire input collection?
some
terminates early, and does no extra work.
filter
returns a lazy seq which does the work in chunks. The last time I checked the chunk size is 32, so we may end up making 32 unnecessary calls of the predicate in the worst case.
That being said, if used as a transducer (arity-1), filter
again doesn't do any extra work.