Fork me on GitHub
#beginners
<
2022-03-05
>
Neil Barrett17:03:25

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?

seancorfield23:03:59

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.

Neil Barrett00:03:00

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!

1
hiredman18:03:33

Checkout page 195

hiredman18:03:42

We must have different editions or something , the treenode stuff is not on 216 in mine

seancorfield23:03:35

First Edition has it on page 195, Second Edition, it's on page 216.

hiredman18:03:18

195, which is a few pages after the treenode defrecord is introduced, extends the protocol to nil

zeitstein19:03:58

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?

Alex Miller (Clojure team)19:03:41

There are some known issues related to transit stream flushing that you could be seeing

Alex Miller (Clojure team)19:03:36

Is this transit-clj on server side?

zeitstein20:03:53

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?

zeitstein20:03:43

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.

zeitstein20:03:46

Ah, I see Transfer-Encoding: chunked. It would seem the linked issue is indeed relevant.

Alex Miller (Clojure team)21:03:31

Yeah, I think you're probably getting a large number of tiny packets

zeitstein21:03:12

(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!

😯 1
1
Alex Miller (Clojure team)21:03:22

This is on our radar to work on, just haven't gotten a chance yet

👍 2
1
Drew Verlee20:03:03

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" ""])

jaihindhreddy20:03:05

I'd prefer filtering and taking the first. I've seen (def ffilter (comp first filter)) in multiple codebases. Not very satisfying TBH.

Drew Verlee20:03:14

yea. I mean, this usually means something is wrong with the modeling upstream. But generally not worth fixing.

nate21:03:31

In this case, negating might make it clearer:

(first (remove str/blank? ["yolo" ""]))
reads as "give me the first non-blank string"

👀 1
1
ahungry22:03:28

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))))

ahungry23:03:20

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 🙂

jaihindhreddy08:03:16

> 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.

👍 1