This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-11-13
Channels
- # aleph (2)
- # announcements (1)
- # beginners (133)
- # cider (29)
- # cljdoc (9)
- # cljs-dev (2)
- # cljsjs (3)
- # cljsrn (1)
- # clojure (146)
- # clojure-dev (26)
- # clojure-europe (3)
- # clojure-italy (26)
- # clojure-japan (6)
- # clojure-nl (76)
- # clojure-spec (4)
- # clojure-uk (42)
- # clojurescript (17)
- # cursive (43)
- # datascript (1)
- # datomic (28)
- # emacs (4)
- # figwheel-main (13)
- # fulcro (26)
- # hyperfiddle (2)
- # jobs (9)
- # jobs-discuss (6)
- # leiningen (1)
- # mount (5)
- # onyx (8)
- # pathom (5)
- # pedestal (2)
- # re-frame (52)
- # reagent (21)
- # reitit (58)
- # ring-swagger (24)
- # shadow-cljs (95)
- # sql (14)
- # test-check (10)
- # yada (18)
I'm not sure, but roughly reasoning from the idea that Clojure reads your code (and that is when reader conditional processing occurs), and then later compiles your code, which is when macro expansion occurs, I do not see how it is possible to write such a macro.
Unless you consider cases where the macro generates code that causes the reader to explicitly be invoked again on strings.
But don't take my guess here as authoritative on your question.
well, I don’t actually need to run the code in the same JVM, I only want to generate a form and emit it to a file, so I can run it later
e.g. this one:
(defn main-form []
`(defn -main [& args]
(time (run-tests 20))
#?(:clj (shutdown-agents))))
So you need to write a program that writes as its output the text of another program? You don't need a macro for that, do you?
you can detect the macro compilation environment with a trick @borkdude https://github.com/plumatic/schema/blob/master/src/clj/schema/macros.clj
the program writes a program that will be executed in a different process. let me rephrase the question: how can I generate a quoted list with a reader conditional in it
Try (reader-conditional '(:clj (shutdown-agents)))
Not sure if that is what you are looking for, but it may be.
Oops: I mean (reader-conditional '(:clj (shutdown-agents)) false)
Once we have established that something (A) is equal to another thing (B), then why would a set containing just A not be equal with a set containing just B?
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(= jud jst))
;; => true
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(= #{jud} #{jst}))
;; => false

I have spent hours writing an article and tracking down quirks in Clojure/Java's = and hash, and this one has me stumped after 3 minutes of thinking about it and experimenting at a REPL. Huh. You have me intrigued to try to find out what is going on here, because I'm not sure yet.
BTW, in case it might help you figure out the answer, here is that article: https://github.com/jafingerhut/thalia/blob/master/doc/other-topics/equality.md
The fact that the two objects are =
according to clojure.core/=
, and have equal clojure.core/hash
return values, and yet #{jud}
is not equal to #{jst}
, has me baffled.
Another weird thing is this: (contains? #{jud} jst)
returns false
It is definitely true that if you stick with immutable values and collections in Clojure, that =
and hash
behave the way you would want them to, mathematically, even for deeply nested collections.
at least most of the time. It seems you may have found another exception to that rule that I haven't seen before. ##NaN
values in Clojure are not =
to themselves, is another exception, and also make collections they are part of never =
, but that exception a lot of people already know about.
Oh, here is another clue:
=
is not symmetric for these two objects, probably because Java .equals
method is not, either:
(def jud (java.util.Date.))
(def jst (java.sql.Timestamp. (.getTime jud)))
(= jud jst)
;; => true
(= jst jud)
;; => false
(.equals jud jst)
;; => true
(.equals jst jud)
;; => false
(= (hash jud) (hash jst))
;; => true
(= (.hashCode jud) (.hashCode jst))
;; => true
(= #{jud} #{jst})
;; => false
(= #{jst} #{jud})
;; => true
(contains? #{jud} jst)
;; => false
(contains? #{jst} jud)
;; => true
I think the root of this issue is that (.equals jst jud)
is false, because of how the Java equals
method is implemented for objects of type java.sql.Timestamp
Here is a quote from the Java documentation for class java.sql.Timestamp
: "The Timestamp.equals(Object) method never returns true when passed an object that isn't an instance of java.sql.Timestamp, because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashCode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation."
Clojure =
does not attempt to mask the underlying behavior of Java .equals
method for you, except for a few classes like Long, Integer, Byte, Short, etc.
Thanks for your elaborate research Andy. You are right, but it is scary that =
isn't commutative in clojure for these cases. There are lots of advantages running on top of an existing large platform, but obviously som disadvantages as well...
This got me thinking about the ordering of associating data into the map, and shure enough;
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(assoc {jud 'ok} jst 'nok))
;; => {#inst "2018-11-14T08:24:01.039-00:00" ok,
#inst "2018-11-14T08:24:01.039000000-00:00" nok}
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(assoc {jst 'ok} jud 'nok))
;; => {#inst "2018-11-14T08:22:49.234000000-00:00" nok}
And
...
(count (conj #{jud} jst)))
;; => 2
while
...
(count (conj #{jst} jud))
;; => 1
This all makes sense to me now, so thanks again.No worries. Rich Hickey has said before something to the effect of "I can't fix Java equals behavior for you, while being a hosted language". Java equals is under the control of the decisions of others, and as Rich argues in multiple talks, equals is broken for mutable objects anyway, in deeper ways than this example you have found.
There isn't any reasonable way for Clojure =
to be made symmetric for Java objects if Java equals
is not symmetric for those objects. (I'm using symmetric as a synonym for commutative there)
I think @qrthey that java mutable stuff might not have the same deep-equality guarantees for this.
both values hash to the same value
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(= (hash jud)
(hash jst)))
;; => true
Same goes for list/vectors of these types
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(= [jud] [jst]))
;; => true
It is just that sets don't work for this. There is probably an explanation, and I would like to understand this better, but in any case this is rather unexpected.
The values are considered 'different' as they both will co-exist in a set.
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(count #{jud jst}))
;; => 2
well, probably people have looked at the source of merge and decided it’s a good conj replacement 🙂
Thanks for your input @mpenet and @orestis. It would be nice to have some insight from the language maintainers on the reasons for the set weirdness though.

but as keys, they are not equal again
(let [jud (java.util.Date.)
jst (java.sql.Timestamp. (.getTime jud))]
(assoc {jud 'ok} jst 'nok))
;; =>
{#inst "2018-11-13T11:17:02.919-00:00" ok,
#inst "2018-11-13T11:17:02.919000000-00:00" nok}
They are mutable. Imagine a set that behaved the way that you are wishing for. Have two dates that are different put into the set. Then mutate one of them so that they are equal. The set would then contain two values that are the same in violation of it's constraints
FYI, mutability wasn't the root cause in this case. It is that Java equals
method is designed not to be symmetric for objects of type java.sql.Timestamp
. They even document it as such on the Java doc page for that class.
It is certainly a good idea to warn people about putting mutable objects into Clojure immutable collections, of course.
just curious, why (keys {:test "a"})
returns [:test]
, but not #{:test}
?
set semantics seems more valid in this context - no order, no duplication
i see…
thanks
@kirill.salykin you are right, but there are some nice things about the implementation. If you also take vals
from the map, you get vector semantics as well, which is nice as the order of the keys
result will match the order of the vals
result.
(let [v {:a 1 :b 2 :c 3}]
(= v (zipmap (keys v) (vals v))))
i am not sure order is applicable in this case
@dpsutton Thanks, that is a valid reason. It goes on to demonstrate that equality is undefined when dealing with mutable values.
i mean I doubt order of keys
garantied to be same as vals
seq, keys, vals order are guaranteed to be the same on a map
oh, didnt know it thanks
It’s mentioned in the docstring I believe
> “Returns a sequence of the map’s values, in the same order as (seq map).”
this part I assume
Tough thing about mutability and equality is you need to talk about "when" they are equal and when they become equal
@qrthey thanks as well
I want to filter a list of elements in a xml file by iterating the list with zip/right and zip/remove the elements which I want to filter out. The problem is, that zip/remove will move the loc to the deepest element of the previous node. Is there an easy way to keep the loc at the same level it was before removing?
@benoit I thought this was only useful for "filtering" in the sense of "searching" for specific elements. I want to modify the zipper to then export it as a new xml document. I'll take a closer look to data.zip.xml, thanks
Is there an idiomatic version of this construct?
(fnil conj #{})
I have some code where I often call (update m k #((fnil conj #{}) % some-val))
to add some value to a set in a map. Is there a better way?
you don't need to make the #() there - (update m k (fnil conj #{}) some-val)
does the same thing
I'm trying to upgrade to clojure 1.9. I've successfully been using data.avl/sorted-maps but it returns two-elem vectors instead of actual mapentries, and 1.9 seems stricter, as (key/val %) doesn't anymore work on two-elem vectors. I can fix my code, but avl makes the same assumption internally, for example in its (subrange). is there a workardound or best practice to deal with this?
yeah I was pretty surprised. I couldn't find anything about it though, but somebody else must have run into it
It is conceivable you are the first person to notice. I suspect Clojure's built in sorted sets are used a lot more than data.avl's.
I think it was changed temporarily during the tuple foray sha: ae7acfeecda1e70cdba96bfa189b451ec999de2e
hmm interesting. the project that uses data.avl/sortedmap has always used 1.8 AFAIK and it's worked fine, didn't think much of it
and all the times I've done (for [x y] (key x))
y has been something that started as a map
for VER in 2 3 4 5 6 7 8 9; do echo 1.$VER.0; clojure -Sdeps "{:deps {org.clojure/clojure {:mvn/version \"1.$VER.0\"}}}" -e '(key [1 2])'; done
bikeshed of the shell code: $(seq 2 9)
sooo... sounds like key/val for 2-elem vectors is not coming back? why does (first (first {1 2})) work, or is that gonna go away as well? in the meantime, I forked and fixed data.avl for my purposes
From the discussion above, it isn't clear to me what "went away". Did anything change across Clojure versions here that is relevant to data.avl?
Also the first first {1 2} uses the fact that mapentry implements seq which isn't going away since that would be a breaking change
@kaosko Ghadi's little shell script above is evidence that Clojure 1.8 also throws an exception if you try (key [1 2])
on a vector.
It isn't just Clojure 1.9. It is all Clojure versions since Clojure 1.2. Perhaps the root cause of the issue is something else?
@andy.fingerhut hmm how odd, must be something else then
It could be that something changed from Cloure 1.8 to Clojure 1.9 such that data.avl is now returning 2-element vectors, when it used to return map-entries?
yeah cljs repl prints: app:cljs.user=> (type (first (clojure.data.avl/sorted-map 0 0))) cljs.core/PersistentVector
whereas clj: (type (first (clojure.data.avl/sorted-map 0 0))) => clojure.data.avl.AVLNode
You may want to try asking on the #clojurescript or #cljs-dev channels to see if the ClojureScript developers are aware of any recent changes in that area.
If the issue you are seeing is specific to ClojureScript
~@(when (some-condition) ....)
should work, if the ... expands to a list or vector
I’m trying to make a thing with reify
that inserts methods if there is a key in the map passed in, so something like:
(defmacro reify-foo [opts]
`(reify Foo
(when ~(opts :foo)
(foo [this]))))
I would have done ~@(when (opts :foo) ...)
because your version always has the foo inside the when in the output form, which won't work with reify
here’s the actual thing:
(defn websocket-listener
([{:keys [on-binary on-close on-error on-open on-ping on-pong on-text]}]
`(reify WebSocket$Listener
~@(when on-binary
(onBinary [this ws byte-buffer last?]
(on-binary ws byte-buffer last?))))))
then you might need quoting around (foo [this] ...)
thanks to the surrounding unquote, but that's not hard to do
yeah, I think you need another backtick around (onBinary ...)
otherwise the macro tries to execute onBinary which is likely nonsense
ahh, here we go! 😄
(defn websocket-listener-fast
([{:keys [on-binary on-close on-error on-open on-ping on-pong on-text]}]
`(let [ob# ~on-binary]
(reify WebSocket$Listener
~@(when on-binary
`(onBinary [this# ws# byte-buffer# last?#]
(ob# ws# byte-buffer# last?#)))))))
sometimes breaking some of it out into a helper function can make it clearer what's happening
minimal example of the principle though
Clojure 1.9.0
(ins)user=> (defmacro foo-object [with-bar?] `(reify Object ~@(when with-bar? `[(toString [this] "bar")])))
#'user/foo-object
(ins)user=> (foo-object true)
#object[user$eval150$reify__151 0x23fb172e "bar"]
(ins)user=> (foo-object false)
#object[user$eval154$reify__155 0x5e1fa5b1 "user$eval154$reify__155@5e1fa5b1"]
@schmee are you sure that onBinary inside the when isn't missing another layer of nesting for the @ to consume?
@lilactown at least macros are 100% data-y :D
this one is tested and actually works 😂
(defn websocket-listener-fast
([{:keys [on-binary on-close on-error on-open on-ping on-pong on-text]}]
`(let [~'ob ~on-binary]
(reify WebSocket$Listener
~@(when on-binary
[`(~'onBinary [_# ws# byte-buffer# last?#]
(~'ob ws# byte-buffer# last?#))])))))
I wouldn't have noticed except that same issue tripped me up in my minimal example (and the error you get when expanding the macro is really unhelpful...)
@schmee I don't know if I understand the whole context of what you want to do, but you may be able to use a function instead of a macro and conditionally specify!
the protocols/methods onto a plain object at runtime
is specify! new?