Fork me on GitHub
#clojure
<
2021-02-05
>
bendy07:02:02

Currently I have a function like follows:

(cond
  (-> db/query-a seq)
  (-> db/query-a first process)

  (-> db/query-b seq)
  (-> gb/query-b first process)

  ...

  (-> db/query-n seq)
  (-> db/query-n first process)
However, this means that for every query, I have to make two trips to the database. What would be the best way to rewrite this in clojure so it only has to make one query per condition, and does not have to make the subsequent queries until knowing that the previous queries are empty?

p-himik07:02:02

(if-some [qa (not-empty db/query-a)]
  (-> qa first process)
  (if-some [qb (not-empty db/query-b)]
    (-> qb first process)
    ...))

bendy08:02:29

thanks @U2FRKM4TW! not-empty looks neat, I poked around for a cond-some and stumbled upon https://github.com/Engelberg/better-cond which I will give a go

nbardiuk09:02:28

(condp #(-> (%1 %2) seq) db
    db/query-a :>> #(-> % first process)
    db/query-b :>> #(-> % first process))
https://clojuredocs.org/clojure.core/condp has an overload that allows to pass result of condition to the function

bendy10:02:46

@U076FM90B That's perfect! It will take me a while to parse through but I will try to use it - good learning op 😄

noisesmith17:02:12

yeah, condp is a little weird (eg. how it scrambles the args provided), but can really DRY up code

noisesmith17:02:48

also, this pattern could be replaced by "or", since the condp function is pure glue with hardly any logic:

(process
  (first
    (or (seq db/query-a)
        (seq db/query-b)
        ...
        (seq db/query-n)))))
(first already implicitly calls seq, but it's safe to call it twice)

👍 3
noisesmith17:02:35

but you would want to stop if none were true, but that's a simple flip

(some-> (some seq [query-a query-b ... query-n])
        (first)
        (process))

noisesmith17:02:59

(I also realized a coll plus some seq is simpler than calling seq on each item)

Falco11:02:05

I need your help. I do remember a Clojure environment mentioned in a blog post. As far as I recall this was a standalone environment (single JAR?) and praised by the author for its simplicity and beginner friendliness, because it meant that you could instantly start coding from anywhere without a full-blown IDE. Does this ring a bell with anyone? Is it possible that this tool has been shipped with the official Clojure distribution in earlier times? It might also have been part of another tool, or maybe ClojureScript. I tried googling a lot, but to no avail ("clojure gui" is a pretty overloaded search term -.- )

Falco11:02:41

Looks interesting, thank you. But somehow I remember having a native app.

javahippie11:02:04

When it was some time back, maybe it was LightTable? http://lighttable.com

Falco11:02:08

@U0N9SJHCH thanks for brining it up 👍

noisesmith16:02:40

yeah, light table got a bunch of buzz and had some high quality talks hyping it, but the project seems to have fizzled

noisesmith16:02:25

oh, it's still under development

bronsa11:02:10

https://github.com/oakes/Nightlight (successor to https://github.com/oakes/Nightcode which is probably the one you were thinking of)

Falco11:02:53

Thanks, that is indeed a good candidate... if my memory just wasn't so blurry 🙈

Shantanu Kumar12:02:38

Both repos are archived on Github

Quentin Le Guennec15:02:51

Hello, hash on a zipper returns a different value at each call. Does anyone know why?

Quentin Le Guennec15:02:50

(a consequence of this is that = isn't consistent either)

p-himik15:02:58

If you define your functions inline, that's probably why:

cljs.user=> (= (fn []) (fn []))
false
cljs.user=> (hash (fn []))
1
cljs.user=> (hash (fn []))
2

Quentin Le Guennec15:02:22

Yes, I figured it out. I think that's the reason.

andy.fingerhut15:02:13

= and hash on Clojure functions are based on identity of that function object in memory.

Quentin Le Guennec15:02:33

Yeah, that makes sense.

andy.fingerhut15:02:16

I have not looked at the implementations of zippers in Clojure carefully -- are you saying that they involve storing reference to Clojure functions inside of the data structure?

andy.fingerhut15:02:06

Hmmm. OK. I somehow thought they had ways of representing them without doing that -- but again, I have never looked closely.

Quentin Le Guennec15:02:25

Oh no, sorry, misread your question

Quentin Le Guennec15:02:45

I am myself storing functions in the zipper, that doesn't have anything to do with the implementation itself

andy.fingerhut15:02:26

OK. Doing that will cause the same issues of those functions having = and hash based on identity, no matter which immutable collections you make them a part of in Clojure.

Quentin Le Guennec15:02:14

a function reference defined with def should work though?

andy.fingerhut15:02:37

Depends upon what you mean by "work".

Quentin Le Guennec15:02:52

I mean that = and hash should be consistent

andy.fingerhut15:02:06

If I do (defn foo [x] (inc x)) followed by (def bar foo), then (identical? foo bar) should be true.

andy.fingerhut15:02:45

If I do (def foo (fn [x] (inc x))) followed by (def bar (fn [x] (inc x))) then (identical? foo bar) should be false.

andy.fingerhut15:02:26

Each invocation of defn or fn creates a new function object.

andy.fingerhut15:02:07

There might be exceptions to that statement, but I'm hard pressed to think of one, and relying on such behavior seems very fragile to me.

Quentin Le Guennec15:02:44

yeah, a function identifier and a identifier->function map seems more reliable

andy.fingerhut15:02:16

Using an immutable value like a string, keyword, or symbol as an identifier -- definitely.

andy.fingerhut15:02:41

If it matters to you to be able to distinguish such immutable values by some kind of 'type' from other immutable values, you could even create a new type with defrecord that has one field that is the string/keyword/symbol, but that might be overkill for your needs.

DG17:02:18

Hi, I think I’ve hit a bug when using compojure-api with ring-swagger. When using a custom JSON encoder the swagger.json file also changes, rendering the spec invalid. More details here https://github.com/metosin/compojure-api/issues/449 - any ideas?

Takis_17:02:03

hello ,

ITransientMap tm = (ITransientMap) transientC.invoke(PersistentHashMap.EMPTY);
and many assoc after using java interop , works

Takis_17:02:21

ITransientMap tm = (ITransientMap) transientC.invoke(PersistentArrayMap.EMPTY);

Takis_17:02:35

and many assoc after using java interop , doesnt work

Takis_18:02:55

i am using them in wrong way? i know that ArrayMap becomes auto HashMap

Takis_18:02:00

its not a problem i just used the HashMap,but why ArrayMap didnt work? only some members added,2 keys that had big values didnt added

noisesmith18:02:51

@takis_ transients only accidentally update in place, the correct way to use them is to use the return value of the updating function in place of the original object

noisesmith18:02:24

as you've seen, adding to it does work as mutation for a few thing, until it stops working

Takis_18:02:16

oh its my fault, i knew that transients works as persistent,i will retry it,i forgot the assign again

Takis_18:02:31

yes now its fine it was bug i forgot it , thank you noisesmith 🙂

Takis_18:02:05

for perfomance reasons,what to use?

Takis_18:02:17

i should start with ArrayMap or HashMap?

Takis_18:02:51

some maps can be very small in my app even 2 members

Takis_18:02:32

anyway i will test it i guess ArrayMap better , thanks :)

noisesmith18:02:28

ArrayMap tends to work best for small sizes (and auto-promotes to HashMap when it gets larger)

vemv19:02:45

I'm playing a bit with compile. If I (compile 'rebel-readline.main), a rebel_readline dir gets created inside classes, containing clojure (and likewise for other transitive deps):

$ ls classes/rebel_readline
clojure/                                                               jline_api$char_at.class
...
Is that expected behavior? It seems odd to me as a more normal structure would be to have clojure and rebel_readline as sibling dirs, inside classes

borkdude19:02:14

@vemv it's probably expected since rebel-readline has namespaces with clojure in it: https://github.com/bhauman/rebel-readline/tree/master/rebel-readline/src/rebel_readline/clojure

vemv19:02:09

ahhhh that confused me lots :) I thought I was looking at the real clojure.main

borkdude19:02:31

clojure main is probably not compiled since there is already a compiled version on the classpath while you're compiling

👍 3
borkdude19:02:48

(which is maybe not allowed by our bd, but that's a different issue)

Jeremy Cronk19:02:07

If I’m dealing with a collection of Java objects, say [i1 i2 i3 i4] and I need to do something like (.setResult i1 i2) then (.setResult i2 i3), etc., what’s the best way to do that? It looks like a reduction to me, but it’s only being done for side effects, so it doesn’t need to return anything. I think I’m just confusing myself at this point, because it seems like there should be a simple way to do this. Any suggestions?

phronmophobic20:02:13

you can use run!:

(run! (fn [[a b]] (.setResult a b)) 
      (map vector coll (rest coll)))

👍 3
Jeremy Cronk20:02:11

Yes! This is the half-remembered function I was searching for. Thanks!

👍 3
noisesmith20:02:40

with doseq you don't need the fn

(doseq [[a b] (partition 2 1 coll)]
  (.setResult a b))

Quentin Le Guennec20:02:25

Hey, will = be perform in O(1) time if the output of hash function for its arguments is the same?

dpsutton20:02:13

i believe by the pigeonhole principle it cannot. If we admit there are an infinite amount of hashable items and a finite number of hashes, hash would destroy equality if used in this manner.

Quentin Le Guennec20:02:45

That would make sense, but it's also very unlikely

andy.fingerhut20:02:36

There are only 2^32 different 32-bit hash values.

andy.fingerhut20:02:59

There are far more than 2^32 possible Clojure values that can be hashed and used as a key in a hash map, or an element in a hash set.

andy.fingerhut20:02:09

Many pairs of such values must have the same hash value.

Quentin Le Guennec20:02:35

So I guess the answer is no.

andy.fingerhut20:02:39

If hash is implemented correctly, it is safe to assume that (hash x) being different than (hash y) implies that x is not equal to y

dpsutton20:02:41

and (= (reify Object (hashCode [_] 42)) (reify Object (hashCode [_] 42))) is a direct answer

andy.fingerhut20:02:01

If hash is implemented incorrectly, then even that assumption is not safe.

andy.fingerhut20:02:04

Regarding "very unlikely" if hash is "evenly distributed" or you use "random values" then with 2^32 possible values, there is fun thing called "Birthday Paradox" (not really a paradox, really more of a 'surprising non-obvious fact') that says whenever you have about the square root of the number of "hash buckets" (2^32 square root is 2^16 = 65,536), then you are 50% likely to have a collision.

👏 3
andy.fingerhut20:02:29

That is for "uniform randomly distributed values" -- it is of course possible to know more about your data set and how they are distributed, that could change that likelihood of collision.

Quentin Le Guennec20:02:59

Hmm I see, yes I heard about that before

andy.fingerhut20:02:36

So if you have a hash map or hash set with 65,536 items, about half the time at least two of those will collide in their hash values, and Clojure's implementation will put such items in a linear linked list.

andy.fingerhut20:02:54

(a separate linear linked list per hash value, not one common linked list for the hash-map/hash-set as a whole)

Quentin Le Guennec20:02:58

Oh ok, that's interesting

andy.fingerhut20:02:52

That Birthday Paradox is one of the reasons that UUIDs have as many bits as they do. 32 bits is far too small for the use case that UUIDs are for.

Quentin Le Guennec20:02:55

So that means the colliding values in a hashset are tested not against their hash, but in a O(n) comparison

andy.fingerhut21:02:37

The colliding values in a hash set all have equal hash values, so they cannot be told apart by their hash values alone. You have to do a linear search and do clojure.core/= on the searched value and the ones in the collection that collide.

andy.fingerhut21:02:14

Even if there are no collisions, you have to do clojure.core/= on a searched-for value and the one in the collection with the same hash, because even though they have the same hash, they might return false for clojure.core/=

dpsutton20:02:29

(count (into #{} [(reify Object (hashCode [_] 42)) (reify Object (hashCode [_] 42))]))