Fork me on GitHub
#beginners
<
2020-09-09
>
Jack Arrington12:09:57

Is it normal to define multimethods like this (defmulti my-fn #(mapv type %)) to achieve a sort-of function overloading in Clojure?

baptiste-from-paris16:09:11

can you be more specific ?

Jim Newton13:09:54

what's a good way to count the number of times a given predicate function returns true when mapped over a sequence? E.g., (count-matches odd? [1 2 1 3 -4 -5]) should return 4. Perhaps (count (filter ???)) but there's really need to create a new sequence.

delaguardo13:09:40

(count (sequence (comp (map odd?) (filter identity)) [1 2 1 3 -4 -5]))
transducers should not create intermediate sequences

pithyless14:09:39

@U010VP3UY9X are you worried about the GC from doing?

(count (filter odd? [1 2 1 3 -4 -5]))
That seems like an unnecessary concern.

Jim Newton14:09:20

In this case, I only want to know whether count is > 1. I guess I could write my own with reduce/reduced

Jim Newton14:09:03

or perhaps (not (empty? (rest (filter odd? ...)))) BTW odd? is just an example, in my case the predicate is more costly.

pithyless14:09:24

(->> (filter odd? [1 2 1 3 -4 -5])
     (drop 1)
     (some?))

Jim Newton14:09:57

so I need the parens around (some?) ?

pithyless14:09:17

nah, that's just my convention; it's actually (->> (filter ..) (drop 1) seq some?) or perhaps (->> (filter ..) (drop 1) empty?)

pithyless14:09:44

so, just checking if the seq has more than one item after being filtered

pithyless14:09:13

hell, you can actually just check if (second (filter ...)) exists 😄

Jim Newton14:09:24

🙂 transducer shmansducer

pithyless14:09:33

My original point still stands - don't be so worried about the performance of lazy-seqs; unless you can measure actual slow performance. In this case, you do probably just want (second (filter pred some-coll)) ;]

delaguardo14:09:39

(second (filter ...)) this will not work if second element is nil )

✔️ 3
Jim Newton14:09:24

what about just (rest (filter pred some-coll)) but as I understand an empty collection is not false. That's why I said (not (empty? (rest (filter red some-cool))))

Jim Newton14:09:31

but I think there's an unintuitive abbreviation for` (not (empty? ...))`

pithyless14:09:14

There can be no nil, because it would have been filtered out

pithyless14:09:30

There is also not-empty if you are so inclined; or next that will return nil only if the rest is an empty list.

pithyless14:09:56

> There can be no `nil`, because it would have been filtered out ah right -- I didn't think of (pred nil) => something-truthy

dpsutton14:09:18

there's both bounded-count and seq that can quickly tell you if the count is greater than 0

pithyless14:09:46

(nil? (next (filter pred some-coll))) (< (bounded-count 2 (filter pred some-coll)) 2)

Jim Newton15:09:17

> There can be no `nil`, because it would have been filtered out I don't understand this claim. Of course I can have a predicate which returns true when its argument is nil among others

Jim Newton15:09:48

bounded-count, that's cool.

Malik Kennedy20:09:33

I think (seq x) is the better way to do (not (empty? x)) https://clojuredocs.org/clojure.core/empty_q

Jim Newton12:09:20

Yes I've had this discussion several times before. For me seq does not express the intent of (not (empty? ...)) and in my opinion the compiler should compile them to the exact same instructions, but it fails to (or refuses to) do so.

Jim Newton12:09:32

If programmers should always use (sec ...) in place of (not (empty? ...)), then the compiler should do that for me.

Alex Miller (Clojure team)15:09:09

that is an ancient page :)

sova-soars-the-sora16:09:35

are there any clojure-based analytics for web servers?

dharrigan20:09:16

Following on from a discussion in #clojure-uk, I'm trying to wrap my head around the information presented here

dharrigan20:09:39

In particular, it's this sentence, near the end that I can't seem to understand...

dharrigan20:09:52

Since Var roots are global for the entire application, once the body of the future gets around to deref-ing the var (after the 1 sec pause) the root value has already been reset back to 0, causing a race condition.

dharrigan20:09:16

I'm trying this out in a REPL and I observe the behaviour as presented in the post

dharrigan20:09:43

I just don't understand however, what is meant the root value has already been reset back to 0, causing a race condition.

dharrigan20:09:07

what is setting the value back to 42 and why a race condition?

dharrigan20:09:41

What is throwing me is this the root value has already been reset back - it suggests to me that something behind the scenes is changing the value of the var back to the value of 42.

seancorfield20:09:59

What if the future executed more quickly, before the with-redefs completes? That's your race condition.

dharrigan20:09:49

Is that what is happening here?

dharrigan20:09:27

The with-redefs in this example doesn't seem to be doing anything much, it's wrapping a future.

seancorfield20:09:01

user=> (defn foo [] 0)
#'user/foo
user=> (with-redefs [foo (fn [] 42)] (future (println (foo))) (dorun (range 1)))
nil
0
user=> (with-redefs [foo (fn [] 42)] (future (println (foo))) (dorun (range 1)))
42
nil
user=> 
The same code with different results.

jsn20:09:02

@dharrigan after with-redefs completes, the value is reset back to what it was before with-redefs

dharrigan21:09:40

@jason358 thanks let me get back to you on that in a second

dharrigan21:09:51

@seancorfield am I missing something as foo is undefined if I try this on my repl

seancorfield21:09:42

Oh, sorry (defn foo [] 0) precedes that.

seancorfield21:09:05

(but foo could be any function -- with-redefs is the issue here)

seancorfield21:09:55

I did another run of it and updated the REPL session shown above.

dharrigan21:09:54

I must have a faster machine 🙂 I have to bump the range up to nearly 1000 to have differing results

dharrigan21:09:23

let me digest what has been said so far 🙂

dharrigan21:09:09

coming back to the article for a second

dharrigan21:09:48

It would make sense to me, if the line was instead of the root value has already been reset back to 0, causing a race condition. it was the root value has already been reset back to 42, causing a race condition.

dpsutton21:09:38

the race is that you have code expecting the value to be 42 and code that will change the value back to 0. the order of those events is not deterministic and therefore you have a race

dpsutton21:09:17

sometimes the future will execute in totality first, sometimes the resetting back to 0 will happen first, sometimes the future will execute some portion with 42, then some portion with 0. its a race

seancorfield21:09:06

And the root value is reset back to 0 after with-redefs completes. It is not reset back to 42.

seancorfield21:09:50

with-redefs temporarily sets it to (return, in my case) 42 -- and then resets it back to (return) 0.

jsn21:09:57

@seancorfield hmm, now I'm confused too. Why? it's def 'ed as 42, so "back" should be 42

jsn21:09:12

it's with-redef 'ed to 0, not 42

seancorfield21:09:18

I'm basing it on my code shown above.

seancorfield21:09:33

Is that not the order the article does it? Let me look...

jsn21:09:05

@dharrigan yeah, I think you've found a typo or something

seancorfield21:09:51

Ah, right, the article starts out with 42 and does binding to 0. My bad. Sorry.

dharrigan21:09:07

Riiigh, so is it a typo then on that line? Should it be reset back to 42, causing a race condition. not reset back to 0, causing a race condition.

seancorfield21:09:10

I assumed, based on the quote, it was doing what I showed. But, yeah, the article should say "reset back to 42".

dharrigan21:09:17

(please say yes)

seancorfield21:09:22

I thought your confusion was something else 🙂

jsn21:09:22

@dharrigan yeah, that's my understanding

dharrigan21:09:30

Now it all completely clicks

seancorfield21:09:57

(I mistakenly assumed the article was correct and you were just confused about why there was a race condition 🙂 )

dharrigan21:09:14

> What is throwing me is this the root value has already been reset back - it suggests to me that something behind the scenes is changing the value of the var back to the value of 42.

dharrigan21:09:21

I mentioned it above 🙂

dharrigan21:09:36

Anyhoooo 🙂 phew

seancorfield21:09:56

I guess I was confused about your confusion! We're all good now.

seancorfield21:09:28

TL;DR: with-redefs is gross (but it is also still very useful if you know you won't have any race conditions)

dharrigan21:09:06

Now I get it, the with-redefs sets the answer to be 0, but it modifies the root binding, only for the duration of that block. When the block exits, the value is put back to 42, in the meantime, the future inside that block will complete at some point and where you the programmer was thinking that the value of the answer would be 0, it's now 42 because it's been reset back

seancorfield21:09:52

Well, it may or may not have been reset back. Hence you may get zero or you may get forty-two.

dharrigan21:09:12

Yes, the race condition stands true - it's dangerous territory 🙂

seancorfield21:09:20

(that's what I was trying to illustrate with my REPL session)

dharrigan21:09:51

Yes, and I appreciate it - it helped to futher solidify my understanding and help figure out that the article was a little misleading 🙂

seancorfield21:09:16

Just to prove it can happen with a simple Var, set to 42 initially and with-redefs'd to 0:

user=> (def answer 42)
#'user/answer
...
user=> (with-redefs [answer 0] (future (println answer)) (dorun (range 1)))
nil
42
user=> (with-redefs [answer 0] (future (println answer)) (dorun (range 1)))
0
nil
user=> 
Took me about half a dozen repetitions to see the value flip.

dharrigan21:09:53

Would it be impolite to reach out to Tim to ask him to review the material and look to update the post?

dharrigan21:09:19

Others may stumble across it (or be linked to it, from a discussion in another channel) and end up being confused too 🙂

seancorfield21:09:25

I think that would be a good idea!

dharrigan21:09:58

Thank you everyone. I've reached out to Tim with the hope he might consider the change to the blog post 🙂

Alex Miller (Clojure team)22:09:23

He's not at Cognitect anymore so he's not going to be able to do that :)

Alex Miller (Clojure team)22:09:55

I didn't actually read all that backchat, but if you could summarize what needs to be updated, I can probably do so

jsn22:09:39

@alexmiller I think at least this one has to be fixed: in the root value has already been reset back to 0, causing a race condition , "0" must be changed to "42".