This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-02-11
Channels
- # announcements (16)
- # aws (4)
- # babashka (30)
- # beginners (58)
- # bristol-clojurians (4)
- # cider (9)
- # clj-kondo (2)
- # clojure (229)
- # clojure-europe (25)
- # clojure-gamedev (1)
- # clojure-italy (4)
- # clojure-nl (13)
- # clojure-sanfrancisco (1)
- # clojure-uk (97)
- # clojured (7)
- # clojurescript (27)
- # code-reviews (2)
- # cursive (30)
- # data-science (39)
- # datomic (14)
- # emacs (12)
- # events (2)
- # fulcro (6)
- # graalvm (8)
- # graphql (14)
- # jackdaw (10)
- # jobs (2)
- # lambdaisland (5)
- # malli (4)
- # off-topic (28)
- # protorepl (13)
- # quil (7)
- # re-frame (2)
- # reagent (1)
- # reitit (3)
- # remote-jobs (5)
- # ring-swagger (1)
- # shadow-cljs (72)
- # sql (4)
- # tools-deps (182)
- # uncomplicate (4)
- # vim (9)
- # xtdb (19)
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!
...and here are the awesome slides: https://gfredericks.com/speaking/2018-11-30-compiling.pdf
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
Thanks folks, I'll take a look at your suggested resources. 🙂
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?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
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
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?
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
Or the output of the javap disassembler. It might help narrow down what is actually changing across different runs.
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
checksums are obviously different, though other differences look like reordering of instructions
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.
I do not have a guess why that might occur, but @U060FKQPN might.
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?
I already did by using the @ in front of his name. Whether he chooses to answer is really up to him.
He may be busy for the next N hours for all I know.
I want reproducible build https://reproducible-builds.org/
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.
The JVM byte code descriptions here: https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings. call them "local variables 0, 1, ..."
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
There are also various dynamic names produced during compilation that are different between builds
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.
I am pretty sure the client_4990
thing contains a value from a global counter, to generate unique names quickly.
And is thus likely to be very sensitive to the order that code is compiled.
e.g. swapping the order that two files are compiled is very likely to change those.
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
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.
Worth a test on your part, perhaps.
What command did you use to create the compiled files?
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.
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).
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.
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.
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?
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
git commit hash is good identifier - but it is not inherent property of produced artifact, while checksum is
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.
Well, my clojure artifact is uberjar 🙂 Application inside need to compile some namespaces in order to provide handlers for pure java libraries it uses
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.
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?
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"]
in other words, what's the equivalent in deps.edn? (in case i don't want to use project.clj)
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
or alternately on the command line you could do clj -J--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED
there is no "always on" java flag option in deps.edn (although that is something we may add)
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}
@andy.fingerhut '
is not a thing in edn
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.
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
for fun (and personal use), i translated racket's cond
into clojure. would anyone care to see it (and give feedback)?
(as seen here: https://docs.racket-lang.org/reference/if.html#%28form._%28%28lib._racket%2Fprivate%2Fletstx-scheme..rkt%29._cond%29%29)
@thomas.ormezzano This might be a stupid idea, but
well, it does exist
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
the problem is that if atom is changing multiple times, you're just sampling here, and you could miss it
that is, if another thread does (swap! atom inc)
twice you might not see the in-between
(the secondary problem is that you are just busy-spinning that thread, which is highly inefficient)
you probably need to back up a step and describe what you're trying to do
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?
Many moons ago (at a previous employer) we used to call these documents design rationale’s.
It feels like a rationale, but they’re usually pretty short.
like a architectural decision record? (http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)
Yes, that’s exactly the term I was looking for 🙂
Thanks!
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?Yeah I considered that, the example is simplified but it will be quite a few sources in the end
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
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.
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
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
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
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
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
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?IIRC :123
should not be valid, but it is due to historical reasons. I.e. undefined behavior.
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…
"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"
some unsupported things kinda-sorta work in some contexts, but not necessarily all (e.g round tripping between printing and reading)
Makes sense. I was just surprised that I can’t eval that keyword in my REPL, yet somehow crux is able to digest it.
which one is accepted by crux, but doesn't work in REPL?
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
It allows you to create all sorts of nightmares:
user=> (keyword "'" "\"/[/]")
:'/"/[/]
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.
People deal with this in various ways, including using strings as keys instead of keywords in some cases.
There should probably be an FAQ for this -- or perhaps there is one or several already whose location isn't known to me.
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}}
Some of it is mentioned here: https://clojuredocs.org/clojure.core/keyword
Cool. I should have checked that list of FAQs.
What is a good ORM database and most compatible with Clojure? Is there any JDk/Driver for that database?
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
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.
It’s probably a better idea to write custom code for the APM than it is to use an ORM
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.
@U2FRKM4TW You are right Elastic APM has trouble making elasticsearch queries compatible
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.
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).
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.
> 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.
(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)
@U2FRKM4TW That's why I put "any" in quotes 🙂
Bottom line: we don't use ORMs in Clojure because we don't have objects -- we use data instead 😉
@U04V70XH6 Exactly our use case. We had to change from immutant to jetty.
is it possible to test that a macro definition properly throws an exception when used incorrectly?
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.
(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)))
(testing ":else can only be used in the last position"
(is (thrown? IllegalArgumentException
(macroexpand '(cond+
[:else :first]
[true :second])))))
doesn't seem to work, gives me a Syntax error macroexpanding cond+
error instead of the expected IllegalArgumentException
hard to get help without code i know, lol
@nbtheduke my version with #' is uglier, but directly gives you the exception the macro throws (if any)
(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
looks like the apply #'
version worked
I wonder what the difference is for thrown?
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
alternatively you could inspect the cause of the exception, but that's less direct with eg. the thrown?
syntax of is
I thought the goal here was to have an (is (thrown? ...) ...)
for a macro in a test
it is
using macroexpand '(cond+ ...
raises a clojure.lang.Compiler$CompilerException
error
while apply #'cond+
raises the original java.lang.IllegalArgumentException
error
alternatively one could implement (compiler-throws? cause (...))
using the expected object from macroexpansion
i expect because macroexpand
raises that error on any exceptions thrown by the macro?
but this is a test clause, not a visual inspection
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]]}
and thrown? doesn't let you specify the cause expected
(is (thrown-with-msg? ArithmeticException #"Divide by zero"
(/ 1 0)))
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
you'd need a new macro
yeah, you can see the IllegalArgumentException
right there, but it's not the exception being returned anymore, it's nested
and I'd agree that this new macro would be cleaner than using var-quote on a macro symbol
(is (thrown-with-msg? ArithmeticException #"Divide by zero"
(/ 1 0)))
(or do you mean the .getCause
chained exception?)
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
there is a variant of thrown-with-msg? that does cause stuff in the clojure test suite
https://github.com/clojure/clojure/blob/master/test/clojure/test_helper.clj#L134
feel free to lift that if needed
and as ghadi says, another option would be to apply some or all of the ex cause handling chain and then verify that
(-> throwable Throwable->map clojure.main/ex-triage clojure.main/ex-msg)
or whatever, going from memory
Throwable->map is a tremendously useful function, and the same representation of an exception happens when you evaluate *e
at a default REPL
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?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)))
(but it's better not to mix ->
/`->>` so I would avoid threading here altogether)
[java-time :as time]
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
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
zone ids as strings is going to get slow...I'm just complaining -- hiredman has the correct analysis
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
I have yet to find a Clojure library that provides significant leverage over just using java.time directly
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
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
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.
Oh, yeah, and it has a name: The Robustness Principle: https://en.wikipedia.org/wiki/Robustness_principle
Thoughtful criticisms of the principle are linked from the Wikipedia article, too, thankfully.
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?@markaddleman deftest
in clojure.test
does that so take a look at its source https://github.com/clojure/clojure/blob/28efe345d5e995dc152a0286fb0be81443a0d9ac/src/clj/clojure/test.clj#L636
Ah, the vary-meta captures the entire form. Thanks!
Whoops. I misread the parens. I think I get what's going on now. Thanks
Given
(def foo (afn ...))
and AOT
will the afn be called during AOT compilation or during execution ?my particular use case is roughly (def foo (System/getenv "foo"))
is that good practice or should I add a delay
Obviously even with AOT it should use the environment variable as defined during execution