Fork me on GitHub
#clojure
<
2020-02-11
>
Rutvik Patel06:02:59

Hey folks, I'm looking for any diagram which explains the whole Clojure compilation process. Specifically, how ASM have been utilized? What would be it's input to generate JVM bytecode. Any remotely related reference also appreciated. Thanks!

sogaiu07:02:10

i haven't gotten too far regarding the asm stuff, but i believe one very relevant file is Compiler.java in clojure's source code

Rutvik Patel07:02:08

Thanks folks, I'll take a look at your suggested resources. 🙂

Alex Miller (Clojure team)13:02:15

Compiler.java is the only relevant file :)

👍 4
sogaiu12:02:36

i happened to be looking at core_proxy.clj today and noticed what looks like some use of asm there too:

(import
 '(clojure.asm ClassWriter ClassVisitor Opcodes Type) 
may be that's a bit divergent from the original query?

tzzh09:02:47

is there a way to await for an atom to have a value ? I am using CountDownLatch from java.util.concurrent and then I can call .await on it - which is going to block until its value is 0 - but I was wondering if there was a way to do that with clojure atoms

vlaaad09:02:12

Not aware of such ways, but you can use a promise and await (deref) it, and add a watch to an atom that delivers to that promise when atom has 0

tzzh09:02:11

OK I see thanks :thumbsup: it might be a bit too much for what I need this time though

angrygami09:02:11

Hello All. I've just find out that each time I do AOT compilation of my clojure source I got different byte code content for my classes e.g.: c6e96979a6b1361ef7aa3c6893cc353a186c3ebf util$getstatus.class c0d56d835a688d2034c13a9aa6b4c1f8ad1c69d6 util$getstatus.class Is there a way to produce byte-to-byte exact results?

andy.fingerhut15:02:25

I would take a look at clj-java-decompiler results from the multiple compilation runs to see if anything changes from one run to the next: https://github.com/clojure-goes-fast/clj-java-decompiler

andy.fingerhut15:02:36

Or the output of the javap disassembler. It might help narrow down what is actually changing across different runs.

angrygami17:02:33

These will be a lot of text... I can put it here. Though I've made diff between javap output and got following:

1c1                                                                                                                     <   MD5 checksum 5451a8596af590278a48eaf4c54f0fc1                                                                       ---                                                                                                                     >   MD5 checksum ad944f6857a2468250c235ade9ea8808                                                                       305c305                                                                                                                 <        215: aload_0                                                                                                   ---                                                                                                                     >        215: aload_1                                                                                                   307,308c307,308                                                                                                         <        217: astore_0                                                                                                  <        218: aload_1                                                                                                   ---                                                                                                                     >        217: astore_1                                                                                                  >        218: aload_0                                                                                                   310c310                                                                                                                 <        220: astore_1                                                                                                  ---                                                                                                                     >        220: astore_0

angrygami17:02:52

checksums are obviously different, though other differences look like reordering of instructions

andy.fingerhut17:02:17

That looks like only the difference between using 'local variable slot 0' and 1 for different purposes across the two runs. Not a functional difference, but yes a byte code difference.

andy.fingerhut17:02:39

I do not have a guess why that might occur, but @U060FKQPN might.

angrygami17:02:08

Can we bug him?

angrygami17:02:27

Or rather "can I bug him?" 🙂

andy.fingerhut17:02:30

Out of curiosity, what is your use case for wanting the JVM byte codes across multiple AOT runs to be the same? Some kind of build/deploy tooling?

andy.fingerhut17:02:57

I already did by using the @ in front of his name. Whether he chooses to answer is really up to him.

andy.fingerhut17:02:18

He may be busy for the next N hours for all I know.

angrygami18:02:26

maven does kinda support it (not without major pain in arse, but does)

andy.fingerhut18:02:03

I highly doubt there is anything in the Clojure compiler that is causing this by design, but could easily imagine there is something that it just happens to pick different local temporary slot numbers (or whatever the correct technical name is for that 0/1 difference) in some cases.

andy.fingerhut18:02:54

The JVM byte code descriptions here: https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings. call them "local variables 0, 1, ..."

angrygami18:02:43

Well as far as I know clojure compiler does use asm quite a lot

angrygami18:02:19

and can produce it's own bytecode without javac help

andy.fingerhut18:02:33

The Clojure compiler does use the asm library to produce JVM byte code, yes. I do not know if the asm library might be introducing this behavior on its own. Perhaps

angrygami18:02:21

There are also various dynamic names produced during compilation that are different between builds

angrygami18:02:59

e.g. client_4990$eval40104.class and client_9628$eval40104.class

andy.fingerhut18:02:49

FYI, it appears that achieving the goal of reproducible Clojure builds, i.e. identical JVM byte code, across runs will require changes to the Clojure compiler, at least, and perhaps the asm library, too. Changes to the Clojure compiler are far and few between in time these days, for stability reasons, so hopefully you are not in a hurry to see an officially released version of Clojure with such changes. You are of course welcome to experiment with your own local changes to these things all you wish.

andy.fingerhut18:02:39

I am pretty sure the client_4990 thing contains a value from a global counter, to generate unique names quickly.

andy.fingerhut18:02:05

And is thus likely to be very sensitive to the order that code is compiled.

andy.fingerhut18:02:21

e.g. swapping the order that two files are compiled is very likely to change those.

angrygami18:02:31

most likely, I would like to have some control over that... and I understand that changes to compiler are unlikely to happen soon if at all, though just hoped that maybe someone solved this using some hacks/tools

andy.fingerhut18:02:12

It seems at least possible that if you compile files in a consistent order, perhaps that would also eliminate the aload_0/1 differences you are seeing.

andy.fingerhut18:02:22

Worth a test on your part, perhaps.

angrygami18:02:54

but how? I use leiningen... and didn't see any options for that...

andy.fingerhut18:02:25

What command did you use to create the compiled files?

andy.fingerhut18:02:54

Whatever it was, perhaps try doing "Lein clean" between two separate build runs, if you weren't already doing that. One Lein build can leave behind many compiled class files in the target (or targets ? I forget) directory, and the compiler typically avoids recompiling Clojure source files when there is a newer time stamp .class file already in the file system.

bronsa18:02:16

do you have a minimal repro of this I could look at?

bronsa18:02:28

but generally the clojure compiler doesn't attempt to be reproducible at all

angrygami18:02:30

I always do lein clean, and this is issue at CI machine, so it even run in it's separate docker container every time. For build I just used :aot key in my project.clj file and give it bunch of namespaces to compile. This list is sensitive to order (that I've figured hard way long time ago). @U060FKQPN Hi 🙂 No I don't, but I probably can do that tomorrow (it is too late here to start).

andy.fingerhut18:02:11

You have probably already considered this factor and avoided it, but using anything like 'latest' or 'release' versions in a Lein project.clj file will go out over the network and look for newer versions of libraries. I do not know whether Leiningen can get different artifacts if you avoid those, guaranteed, or whether there might be reasons it could get different versions on different runs still.

andy.fingerhut18:02:01

I also do not know if anything in Leiningen tries to parallelize compilation at all, but I would guess no. Parallelism is usually a bad thing if you are trying to do things in a consistent order across runs.

andy.fingerhut18:02:33

Out of curiosity, I did a quick search for "clojure reproducible build" and didn't find anything promising there. I saw one email list conversation about one person trying to improve reproducibility in some Debian package for Clojure, and Elena Hashman replying that the change would not by itself make it reproducible, because Clojure builds were not reproducible for other reasons (but without details). Are you trying to make the builds reproducible for a project like Debian?

angrygami18:02:17

I don't use 'latest' version and similar stuff. I use on-premise nexus maven repo which I fully control and no dynamic artifacts can come from internets 🙂 I'm not trying to do anything for Debian. I want reproducible build because I'm making a custom CI/CD infrastructure for our company and having simple way to detect what should be deployed and what could be left untouched (based on file hashes) would be nice

angrygami18:02:55

git commit hash is good identifier - but it is not inherent property of produced artifact, while checksum is

andy.fingerhut18:02:24

Some people deploy Clojure source files rather than AOT compiled .class files, which if acceptable in your case could make the Clojure compiler behavior a non-issue.

angrygami18:02:40

Well, my clojure artifact is uberjar 🙂 Application inside need to compile some namespaces in order to provide handlers for pure java libraries it uses

angrygami18:02:04

so... alas AOT is unavoidable

andy.fingerhut18:02:48

Even uberjar's can contain Clojure source rather than .class files, I am pretty sure, but understood if there are reasons that is not an option for you.

angrygami18:02:21

I only compile what I absolutely have to 🙂

thom11:02:39

does anyone use Aero for config and know if there's a way to have an entire key dependent on a #profile, instead of just a value? That is, in some environments just completely drop the key from the config map?

p-himik11:02:50

It's possible with #merge.

thom11:02:32

ah, so #merge [{:shared :config} #profile {:prod {} :dev {:only :dev}}]

thom11:02:50

that sort of thing at the top level?

p-himik11:02:12

I haven't used it at the top level, but it should be fine.

thom12:02:41

this works, ta.

quadron13:02:51

how do I set the following leiningen project option in my deps file such that my cider repl respects it: :jvm-opts ^:replace [#_"--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED"]

quadron13:02:11

in other words, what's the equivalent in deps.edn? (in case i don't want to use project.clj)

Alex Miller (Clojure team)13:02:57

in deps, you'll need to create an alias: {:alias {:foo {:jvm-opts ["--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED"]}} then use clj -A:foo

Alex Miller (Clojure team)13:02:29

or alternately on the command line you could do clj -J--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED

Alex Miller (Clojure team)13:02:10

there is no "always on" java flag option in deps.edn (although that is something we may add)

✔️ 4
👀 4
andy.fingerhut16:02:42

Does the behavior of the failing call to clojure.edn/read-string make sense to anyone here?

user=> (require '[clojure.edn :as edn])
nil
user=> (edn/read-string "{:a {}}")
{:a {}}
user=> (edn/read-string "{:a '{}}")
Execution error at user/eval829 (REPL:1).
Map literal must contain an even number of forms
user=> (edn/read-string "{:a '1}")
{:a '1}
user=> (edn/read-string "{:a 'foo}")
{:a 'foo}

bronsa16:02:16

@andy.fingerhut ' is not a thing in edn

bronsa16:02:44

so that code is a map with 3 elements: :a, the symbol ' and {}

😯 4
andy.fingerhut16:02:55

weird. I now see in the last two examples that '1 and 'foo are symbols, with the single quote in the name of the symbol.

andy.fingerhut16:02:35

Accidentally ran across this behavior while testing to see which of about 12,000 Leiningen project.clj files can be read with clojure.edn/read

bronsa16:02:17

yeah ' is also a symbol constituent in edn vs not in clojure

Noah Bogart16:02:05

for fun (and personal use), i translated racket's cond into clojure. would anyone care to see it (and give feedback)?

emccue17:02:06

@thomas.ormezzano This might be a stupid idea, but

angrygami17:02:05

You don't need while (it doesn't even exists) @atom is enough

Alex Miller (Clojure team)17:02:10

user=> (doc while)
-------------------------
clojure.core/while
([test & body])
Macro
  Repeatedly executes body while test expression is true. Presumes
  some side-effect will cause test to become false/nil. Returns nil

emccue17:02:38

(defn wait-until-has-value [atom val]
  (while (not= @atom val))

emccue17:02:05

maybe im missing some multithreaded stuff, but this should work more or less

emccue17:02:17

(If i am it falls under voodoo)

Alex Miller (Clojure team)17:02:19

the problem is that if atom is changing multiple times, you're just sampling here, and you could miss it

Alex Miller (Clojure team)17:02:25

that is, if another thread does (swap! atom inc) twice you might not see the in-between

Alex Miller (Clojure team)18:02:01

(the secondary problem is that you are just busy-spinning that thread, which is highly inefficient)

Alex Miller (Clojure team)18:02:47

you probably need to back up a step and describe what you're trying to do

weavejester18:02:49

I have a small naming problem that I’m hoping someone has a good suggestion for 🙂 - What do people think would be the best way to describe a document that justifies a design in a separate document?

rickmoynihan15:02:32

Many moons ago (at a previous employer) we used to call these documents design rationale’s.

weavejester18:02:19

It feels like a rationale, but they’re usually pretty short.

weavejester18:02:20

Yes, that’s exactly the term I was looking for 🙂

markw18:02:55

Hi all - I have a question about how best to approach a problem I'm working on. I'm working with multiple http-streams, each of which submit the same key value pairs with slight variations in the value. I want to compare the most recent values from each stream where the keys are the equal, in as close to real-time as possible. One approach I can think of is to create a channel for each http-stream, fan-in to a single out channel using mix and admix , and then somehow keep track of keys in a map and compare when I have the two I'm looking for. I guess I would loop infinitely in the receiving channel and keep adding to a map, until I have the keys of interest, and then compare. Something like this pseudo-code:

(go-loop [c aggregate-chan 
          lkp {}]
  (when-let [[k v] (<! c)]
   (let [v1 (lkp k1)
         v2 (lkp k2)]
     (if (and (some? k1) (some? k2))
         ;; maybe send to another channel, swap into an
         ;; atom that has a watch, etc
         (do (compare-them! v2 v2) 
             (recur c (assoc lkp k v))
         (recur c (assoc lkp k v))))))
I'd have to keep track of which channels the kv pairs come from (since I could be pulling off values from the mixed channel that both come from the same source), but this is the general idea. Does this seem like a reasonable approach?

bfabry19:02:02

if the number of streams is fixed, I'd use alts! rather than a mix

bfabry19:02:09

well, fixed and small

markw19:02:28

Yeah I considered that, the example is simplified but it will be quite a few sources in the end

markw19:02:26

I'm actually more concerned with how to facilitate the comparison in a non-clunky way than how to get the stream data together in one place

markw19:02:42

something about my decision to just loop associng onto a map, and then compare when I've got enough values seems... off. I mean I think it would work, just feels wrong.

bfabry19:02:00

it's an inherently fuzzy kinda problem I guess

markw19:02:52

I think swapping into an atom which has a map, that then has a watch set on it might be slightly cleaner but I worry if I'm entering over-engineered territory there

bfabry19:02:47

IMO that code would be much more "surprising" to me

markw19:02:37

hmm ok... so maybe it's not completely off base. I'm just getting into using core async and the style is quite different from how i'd write normal clojure code, so I think I'm still developing that gut feel for what is good async code

bfabry19:02:59

obviously there's some consideration that you don't want that map to grow infinitely. also what does compare-them! do? definitely don't want it doing anything blocking

markw20:02:56

the keys will be fixed in amount, so i'm not worried about the map growing because i'll just be overwriting the vals each time for a given key

markw20:02:30

compare-them! is just a stand-in because i don't yet know how i'm going to compare the vals corresponding to matching keys from each stream

bfabry20:02:56

fair enough. yeah imo this is the implementation I'd probably go with

markw20:02:33

alright thanks for taking a look - i'll give it a go

cjsauer19:02:30

Interesting…

user> :123
=> :123
user> (type :123)
=> clojure.lang.Keyword
user> :number/123
=> Exception: invalid token :number/123
Why does adding the namespace suddenly break a numeric keyword?

p-himik19:02:52

IIRC :123 should not be valid, but it is due to historical reasons. I.e. undefined behavior.

cjsauer19:02:26

Hm okay. That’s really odd then. I’m experimenting with crux db and I’m successfully able to transact : as a valid :crux.db/id value, even tho technically that keyword is invalid…

andy.fingerhut19:02:19

"not officially supported" according to http://clojure.org docs, does not mean the same thing as "is guaranteed to throw an exception if you try to use it"

andy.fingerhut19:02:13

some unsupported things kinda-sorta work in some contexts, but not necessarily all (e.g round tripping between printing and reading)

cjsauer19:02:33

Makes sense. I was just surprised that I can’t eval that keyword in my REPL, yet somehow crux is able to digest it.

andy.fingerhut19:02:03

which one is accepted by crux, but doesn't work in REPL?

p-himik19:02:21

@U6GFE9HS7

user=> (keyword "a" "123")
:a/123

p-himik19:02:40

Maybe that's why. Or maybe not. :)

andy.fingerhut19:02:49

Oh, you mean something like :number/123 ? If so, then yes, the Clojure reader that the REPL uses throws an exception if you attempt to read it. If you end up creating such a keyword via some means that doesn't involve the reader, e.g. a call to the keyword function, you can pass it to other Clojure functions, and they will see it as a keyword. If they do not print it out and try to read it back with the Clojure reader, then no exception

cjsauer19:02:29

Ahhh yes, I’m creating it using keyword.

p-himik19:02:19

It allows you to create all sorts of nightmares:

user=> (keyword "'" "\"/[/]")
:'/"/[/]

andy.fingerhut19:02:15

The fact that keyword doesn't prevent you from creating unreadable keywords has been asked N times, an issue was created for Clojure and declined, at least partly for performance reasons. There is an open issue to consider creating a different function that might do that, but no proposed implementation I am aware of. One could implement such a thing themselves, where the most general implementation I can think of would internally try converting it to a string and reading it back, throwing an exception or returning an error if reading threw an exception.

andy.fingerhut19:02:39

People deal with this in various ways, including using strings as keys instead of keywords in some cases.

andy.fingerhut19:02:27

There should probably be an FAQ for this -- or perhaps there is one or several already whose location isn't known to me.

cjsauer19:02:03

So, I should likely avoid these sorts of keywords then. Crux supports a handful of valid IDs (https://opencrux.com/docs#transactions-valid-ids) I can likely use the map flavor: {:crux.db/id {:my/id 123}}

cjsauer19:02:53

Nice, ty for the info and reference

andy.fingerhut19:02:05

Cool. I should have checked that list of FAQs.

Eduardo Mata19:02:40

What is a good ORM database and most compatible with Clojure? Is there any JDk/Driver for that database?

p-himik19:02:15

The best ORM is no ORM. ;)

16
cjsauer19:02:42

I’ve heard from others that using next.jdbc with something like honeysql is quite pleasant. https://github.com/seancorfield/next-jdbc https://github.com/jkk/honeysql

p-himik19:02:15

And none of those are ORMs.

☝️ 16
Eduardo Mata19:02:56

I use hugsql to query my Clickhouse Database. However, I have a self built adapter that is causing some issues with my APM agent. Not a big impact but it is a nuisance.

ghadi19:02:51

It’s probably a better idea to write custom code for the APM than it is to use an ORM

8
p-himik19:02:18

It doesn't have to do anything with ORMs though. Even if you'd find a perfect ORM for Clojure, chances are, it wouldn't support ClickHouse at all.

Eduardo Mata19:02:19

@U2FRKM4TW You are right Elastic APM has trouble making elasticsearch queries compatible

seancorfield19:02:30

No idea what you mean by "ORM database" -- ORMs are wrappers for accessing "any" database and on the JVM those ORMs would all wrap whatever JDBC driver you are using.

seancorfield19:02:49

I've never heard of a the Clickhouse database but not all JDBC drivers are created equal -- and it sounds like APM is relying on features that the Clickhouse JDBC driver does not support (which doesn't surprise me with some of those more esoteric JDBC drivers).

seancorfield19:02:02

If you can switch database vendors to use a more "standard" DB (MySQL, PostgreSQL, SQL Server, etc) then that APM is more likely to work properly with it.

p-himik19:02:36

> ORMs are wrappers for accessing "any" database Not necessarily. There can be DB-specific stuff in a particular ORM. Or some DB might lack a particular piece of functionality required or recommended by an ORM.

seancorfield19:02:13

(this also applies to HTTP servers that you use with APMs -- we wanted to use http-kit but ultimately switched back to Jetty because New Relic's APM didn't fully support http-kit)

seancorfield19:02:25

@U2FRKM4TW That's why I put "any" in quotes 🙂

seancorfield19:02:57

Bottom line: we don't use ORMs in Clojure because we don't have objects -- we use data instead 😉

p-himik19:02:50

Ah, I read the quotes as an emphasis, my bad.

Eduardo Mata19:02:51

@U04V70XH6 Exactly our use case. We had to change from immutant to jetty.

ghadi20:02:03

most APMs want you to stick to a narrow, boring path of supported libraries.

ghadi20:02:17

But I don't get value out of 99% of APMs

Noah Bogart21:02:46

is it possible to test that a macro definition properly throws an exception when used incorrectly?

ghadi21:02:27

You can call macroexpand as a user

4
noisesmith21:02:34

You can call most macros via (apply #'the-macro nil nil args) where the nils take the place of the magic &form and &env args, but macroexpand is the cleaner way to do this.

❤️ 1
noisesmith21:02:35

(ins)user=> (apply #'or nil nil [true false true])
(clojure.core/let [or__5516__auto__ true] (if or__5516__auto__ or__5516__auto__ (clojure.core/or false true)))
(ins)user=> (macroexpand '(or true false true))
(let* [or__5516__auto__ true] (if or__5516__auto__ or__5516__auto__ (clojure.core/or false true)))

Noah Bogart21:02:31

(testing ":else can only be used in the last position"
      (is (thrown? IllegalArgumentException
                   (macroexpand '(cond+
                      [:else :first]
                      [true :second])))))

Noah Bogart21:02:12

doesn't seem to work, gives me a Syntax error macroexpanding cond+ error instead of the expected IllegalArgumentException

Noah Bogart21:02:21

hard to get help without code i know, lol

ghadi21:02:21

Inspect the whole cause chain

ghadi21:02:34

Exceptions have causes

noisesmith21:02:05

@nbtheduke my version with #' is uglier, but directly gives you the exception the macro throws (if any)

noisesmith21:02:39

(cmd)user=> (defmacro foo-validator [& form] (assert (= form [1])) 2)
#'user/foo-validator
(cmd)user=> (macroexpand (foo-validator 2))
Unexpected error (AssertionError) macroexpanding foo-validator at (REPL:1:14).
Assert failed: (= form [1])
(ins)user=> (type *e)
clojure.lang.Compiler$CompilerException
(cmd)user=> (apply #'foo-validator nil nil [2])
Execution error (AssertionError) at user/foo-validator (REPL:1).
Assert failed: (= form [1])
(cmd)user=> (type *e)
java.lang.AssertionError

Noah Bogart21:02:36

looks like the apply #' version worked

Noah Bogart21:02:50

I wonder what the difference is for thrown?

noisesmith21:02:51

it lifts things out of the compiler pipeline, which is weird and ugly (implementation detail args etc.) but also isolates the macro from the compiling machinery somewhat

👌 4
ghadi21:02:51

But we’ve achieved no understanding as to why each thing behaves the way it does

noisesmith21:02:59

alternatively you could inspect the cause of the exception, but that's less direct with eg. the thrown? syntax of is

ghadi21:02:01

Inspect the exception of macro expansion by printing it out

noisesmith21:02:25

I thought the goal here was to have an (is (thrown? ...) ...) for a macro in a test

Noah Bogart21:02:26

using macroexpand '(cond+ ... raises a clojure.lang.Compiler$CompilerException error

Noah Bogart21:02:45

while apply #'cond+ raises the original java.lang.IllegalArgumentException error

noisesmith21:02:28

alternatively one could implement (compiler-throws? cause (...)) using the expected object from macroexpansion

Noah Bogart21:02:28

i expect because macroexpand raises that error on any exceptions thrown by the macro?

ghadi21:02:40

print the whole thing out

ghadi21:02:48

Not just the type

ghadi21:02:03

It’s in there :)

noisesmith21:02:05

but this is a test clause, not a visual inspection

Noah Bogart21:02:08

cond-plus.core=> (macroexpand '(cond+
            #_=>                                  [:else :first]
            #_=>                                  [true :second]))
Syntax error macroexpanding cond+ at (form-init1401409713806008846.clj:1:1).
:else not last

cond-plus.core=> *e
#error {
 :cause ":else not last"
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message "Syntax error macroexpanding cond+ at (/private/var/folders/5w/z50rbwg546x94k8p8jzqfbl00000gn/T/form-init1401409713806008846.clj:1:1)."
   :data #:clojure.error{:phase :macro-syntax-check, :line 1, :column 1, :source "/private/var/folders/5w/z50rbwg546x94k8p8jzqfbl00000gn/T/form-init1401409713806008846.clj", :symbol cond+}
   :at [clojure.lang.Compiler macroexpand1 "Compiler.java" 7009]}
  {:type java.lang.IllegalArgumentException
   :message ":else not last"
   :at [cond_plus.core$cond_PLUS_$cond_loop__17905 invoke "core.clj" 16]}]
 :trace
 [[cond_plus.core$cond_PLUS_$cond_loop__17905 invoke "core.clj" 16]
  [cond_plus.core$cond_PLUS_ invokeStatic "core.clj" 38]
  [cond_plus.core$cond_PLUS_ doInvoke "core.clj" 3]
  [clojure.lang.RestFn applyTo "RestFn.java" 142]
  [clojure.lang.Var applyTo "Var.java" 705]
  [clojure.lang.Compiler macroexpand1 "Compiler.java" 6992]
  [clojure.core$macroexpand_1 invokeStatic "core.clj" 4024]
  [clojure.core$macroexpand invokeStatic "core.clj" 4026]
  [clojure.core$macroexpand invoke "core.clj" 4026]
  [cond_plus.core$eval18112 invokeStatic "form-init1401409713806008846.clj" 1]
  [cond_plus.core$eval18112 invoke "form-init1401409713806008846.clj" 1]
  [clojure.lang.Compiler eval "Compiler.java" 7176]
  [clojure.lang.Compiler eval "Compiler.java" 7131]
  [clojure.core$eval invokeStatic "core.clj" 3214]
  [clojure.core$eval invoke "core.clj" 3210]
  [clojure.main$repl$read_eval_print__9068$fn__9071 invoke "main.clj" 414]
  [clojure.main$repl$read_eval_print__9068 invoke "main.clj" 414]
  [clojure.main$repl$fn__9077 invoke "main.clj" 435]
  [clojure.main$repl invokeStatic "main.clj" 435]
  [clojure.main$repl doInvoke "main.clj" 345]
  [clojure.lang.RestFn invoke "RestFn.java" 1523]
  [nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 79]
  [nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 55]
  [nrepl.middleware.interruptible_eval$interruptible_eval$fn__945$fn__949 invoke "interruptible_eval.clj" 142]
  [clojure.lang.AFn run "AFn.java" 22]
  [nrepl.middleware.session$session_exec$main_loop__1046$fn__1050 invoke "session.clj" 171]
  [nrepl.middleware.session$session_exec$main_loop__1046 invoke "session.clj" 170]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread run "Thread.java" 748]]}

noisesmith21:02:17

and thrown? doesn't let you specify the cause expected

seancorfield21:02:34

(is (thrown-with-msg? ArithmeticException #"Divide by zero"
                      (/ 1 0)))

noisesmith22:02:26

contextually we are talking about the cause as in .getCause - he can't test that the thing that caused the CompilerException was actually an IllegalArgumentException

noisesmith21:02:21

you'd need a new macro

Noah Bogart21:02:40

yeah, you can see the IllegalArgumentException right there, but it's not the exception being returned anymore, it's nested

noisesmith21:02:00

and I'd agree that this new macro would be cleaner than using var-quote on a macro symbol

ghadi21:02:49

I’m not advocating for anything in particular, except exploring and understanding

👍 4
seancorfield21:02:34

(is (thrown-with-msg? ArithmeticException #"Divide by zero"
                      (/ 1 0)))

seancorfield21:02:00

(or do you mean the .getCause chained exception?)

ghadi21:02:17

In Clojure 1.10 some particular chokepoints in the runtime got some normalized error handling https://github.com/clojure/clojure/blob/master/changes.md#21-error-messages

ghadi21:02:39

It is important to understand the what and why around that

Alex Miller (Clojure team)21:02:55

there is a variant of thrown-with-msg? that does cause stuff in the clojure test suite

Alex Miller (Clojure team)21:02:09

feel free to lift that if needed

Alex Miller (Clojure team)21:02:27

and as ghadi says, another option would be to apply some or all of the ex cause handling chain and then verify that

Alex Miller (Clojure team)21:02:51

(-> throwable Throwable->map clojure.main/ex-triage clojure.main/ex-msg)

Alex Miller (Clojure team)21:02:03

or whatever, going from memory

ghadi21:02:03

Throwable->map is a tremendously useful function, and the same representation of an exception happens when you evaluate *e at a default REPL

ghadi21:02:26

debugging smells: analyzing an error by only the error message

Eduardo Mata22:02:35

I am converting dates into date times with US/Central and UTC zone date times as follows:

(as-> time _
    (time/local-date-time "yyyy-MM-dd HH:mm" (str _ " 00:00"))
    (time/zoned-date-time _ "US/Central")
    (time/with-zone-same-instant _ "UTC")
    (time/format (time/formatter :iso-offset-date-time) _)
I normally get a string such as 2020-03-28T06:00:00Z however, today is the first time i got 2020-03-28T05:00:00Z . Any Idea why?

seancorfield22:02:47

as-> is intended to be used inside -> and you do not need it here:

(-> time
    (str " 00:00")
    (->> (time/local-date-time "yyyy-MM-dd HH:mm"))
    (time/zoned-date-time "US/Central")
    (time/with-zone-same-instant "UTC")
    (->> (time/format (time/formatter :iso-offset-date-time)))

seancorfield22:02:21

(but it's better not to mix ->/`->>` so I would avoid threading here altogether)

ghadi22:02:15

what is the time namespace?

Eduardo Mata22:02:49

[java-time :as time]

hiredman22:02:20

there are 3 different timezone conversations in that code where some change to timezone calculations (os updated a zone db, new jvm with a new zone db) would change the result

hiredman22:02:14

using a timezone like US/Central is particularly problematic because US/Central will change relative to utc based on daylight savings time, which can change when it goes in to effect

ghadi22:02:32

wow, that library doesn't look fun

ghadi22:02:40

very mushy w.r.t. the arguments accepted

ghadi22:02:49

I'd create a ZDT directly over anchoring a LDT with a zone

ghadi22:02:24

zone ids as strings is going to get slow...I'm just complaining -- hiredman has the correct analysis

hiredman22:02:16

but basically the answer is, if you use a squishy timezone designator like "US/Central" your date math will be squishy and change as the definition of US/Central shifts

💯 4
ghadi22:02:02

I have yet to find a Clojure library that provides significant leverage over just using java.time directly

hiredman22:02:46

different OSes will also treat timezones slightly differently

hiredman22:02:07

at an old job I would always run the tests on linux and a colleague would run them on osx, and there would be date tests that would always fail for him, because we had this library for parsing weird pathological dates you would get from emails, so we had tests trying to parse weird dates that don't exist (like leap days on years with no leap days) and the underlying date parsing stuff (this is the old java.util.Date stuff) would just make a best guess, and make different guesses depending on the os

ghadi22:02:43

there's an adjective that is applied to certain cryptography libraries: misuse-resistant, which basically means that if you hold it wrong, it doesn't accidentally violate security guarantees In a similar way, I think java.time is very "misuse-resistant" and has a deliberate design, but a lot of wrapper libraries erase portions of that deliberate design

ghadi22:02:13

in service of convenience or some fuzzy "clojure-y"-ness metric

andy.fingerhut22:02:05

Internet RFCs have at least in early decades of their existence encouraged "be conservative in what you send. be liberal in what you accept", which is more a recipe for getting many disparate implementations starting to work with each other, but not so great for security and/or avoiding corner cases later down the road.

andy.fingerhut22:02:38

Oh, yeah, and it has a name: The Robustness Principle: https://en.wikipedia.org/wiki/Robustness_principle

andy.fingerhut22:02:32

Thoughtful criticisms of the principle are linked from the Wikipedia article, too, thankfully.

markaddleman23:02:13

I'm writing a macro and I'd like to attach metadata to the created var. The equivalent of

(def ^::tag s _)
but my macro fu isn't up to the task. Would I handle this in two steps: first define the var and then add the metadata to the var? Is there a better mechanism?

markaddleman23:02:57

Ah, the vary-meta captures the entire form. Thanks!

markaddleman23:02:43

Whoops. I misread the parens. I think I get what's going on now. Thanks

jasonjckn23:02:13

Given

(def foo (afn ...)) 
and AOT will the afn be called during AOT compilation or during execution ?

hiredman23:02:16

it will be called during code loading, which happens during both

jasonjckn23:02:10

my particular use case is roughly (def foo (System/getenv "foo")) is that good practice or should I add a delay

jasonjckn23:02:25

Obviously even with AOT it should use the environment variable as defined during execution

hiredman23:02:43

if you are only reading en environment variable it likely won't matter

hiredman23:02:10

if the function actually does anything of consequence (io, starts threads, etc) it maybe be a good idea to put it in a delay, or even better make it not a def