Fork me on GitHub
#clojure
<
2020-06-17
>
solf05:06:19

Is there any way to retrieve the arglists of special forms like try or defn?

phronmophobic05:06:41

defn isn’t actually a special form, so its arglists can be retrieved from its var meta data, (:arglists (meta #'defn))

phronmophobic05:06:44

getting the arglists for try is a bit trickier, if you look at the source for clojure.repl/doc, you’ll see that the arglist for some of the special forms are hardcoded: https://github.com/clojure/clojure/blob/master/src/clj/clojure/repl.clj#L19

solf05:06:19

Ah correct, defn is not a special form. It is in babashka, which is why I got confused. Thanks for the link to special-doc-map, it's probably what I need

phronmophobic05:06:56

having a special case for the special forms (there’s not many) seems reasonable or ignoring that special-doc is private might be an option ((resolve 'clojure.repl/special-doc) 'try) since core clojure code is pretty stable, but there is a slight risk since it is marked as private

emccue05:06:55

(if anyone was curious, i did in fact crash and burn)

simongray09:06:48

I have inherited a dusty, old Java project that produces a .WAR file. The whole cycle of edit 1) Java code, 2) recompile, 3) build .WAR file, 4) copy it to the relevant folder, and 5) restart Tomcat… seems like a terrible slow feedback loop. Is there any way that I can shorten this feedback loop using Clojure? Like can I take the project code inside a Clojure REPL and dynamically build and redeploy the project?

Matej Vasek11:06:07

I don't know about Clojure solution, however jvm support hotswap, https://www.jetbrains.com/help/idea/debugger-hotswap.html https://stackoverflow.com/questions/42293054/how-to-hot-deploy-code-in-tomcat-running-in-debug-mode , it has limitations you can only modify existing methods (not add new methods), but I found it useful.

simongray11:06:37

Interesting. Thank you for pointing that out.

hiredman16:06:19

you should be able to eliminate building the war file and just point jetty to the files on disk (you would still have to compile the java)

simongray09:06:01

It’s a web service that I need to probe a lot while developing, that’s why I need a much shorter feedback loop.

rmxm09:06:44

Hey guys, first of I would like to express my appreciation for this place 🙂 I am trying to bring some sanity to a large codebase. There is a namespace config, which has a bunch of atoms and functions to access those. My original approach was to eliminate the need for atoms, so I want to load the config file (single file), provide a function get-config that memoizes load-config and redefine all functions accessing values as plain values. This works kinda fine, we are losing some flexibility in terms of loading -> since now we need to restart application to reload config, - that's fine for the large part. However now I am sitting on a different problem - lots of tests mocked the namespace by simply changing the value of atom previously. Simple enough, now its more problematic since the initialization of namespace is loading config value and previous functions accessing certain values were converted to values ~defn~ def since it seemed more natural to access config/server-port rather than (config/server-port) . Any suggestions? To add a bit - I am looking for a facility that would let me reload/refresh an entire namespace so that the values initialized can initialize again according to modified config file - since I can change the actual config file and that would be decent enough. I found this which kinda serves this purpose: https://github.com/clojure/tools.namespace. I am looking more for opinion if what I try is not something idiotic that will bite me down the road 🙂

👏 3
raspasov10:06:03

Have you considered using something like https://github.com/stuartsierra/component (might be a lot of work to integrate, but I think you can start gradually even if it’s not the perfect approach)

rmxm10:06:19

@U050KSS8M yeah, other parts of application will most likely land there in the end - but I consider the parts that land there "stateful".... config is mostly not stateful and application does not require for it reload during runtime... so I think its a waste to "make it stateful" just for the testing piece

rmxm10:06:58

I just require some trivial reload of the namespace so that values can reinitialize

raspasov10:06:33

Got it 🙂 I am not very experienced with tests but I assume you know about https://clojuredocs.org/clojure.core/with-redefs ?

rmxm10:06:40

yeah - but that introduces another set of hurdles, I would need to dynamically inspect the namespace to find all possible values to redef

raspasov10:06:13

I see… sounds like a lot is going on there

rmxm10:06:53

not really, just a couple of values get defined, but redef is not really ergonomic, since you have to always push a block + remember to append values when you add them, where current mocking lib, just produces new config and this config could be easily incorporated given that I can reliably reload namespace in tests

emccue05:06:17

Super lazy approach - you can use load-file

emccue05:06:26

There are benefits to the atoms if you actually want the dynamism

emccue05:06:12

but in general you just need to "drill through" the config or "part of app specific configs" in function arguments

emccue05:06:34

component and co are really good for organizing, but the end result is just a map of components

emccue05:06:47

if you don't have that many moving parts you can make that map manually

emccue05:06:23

(at least thats the epiphany i've had)

emccue05:06:09

so instead of (config/server-port) or config/server-port

emccue05:06:20

(conifg/server-port config)

emccue05:06:41

then in the config namespace you can just have

emccue05:06:52

(def server-port :server-port)

emccue05:06:40

so what i've been doing in my "mess around" repo is

emccue05:06:34

;; ----------------------------------------------------------------------------
(defn load-from-classpath
  "Loads the default config from the classpath"
  []
  ;; Eagerly loads and validates values
  (ConfigFactory/invalidateCaches)
  (let [^Config config (ConfigFactory/load)]
    {:server-port (.getInt config "server.port")
     :db-pool-connections? (.getBoolean config "db.pool_connections")
     :db-name (.getString config "db.name")
     :env (or (opt-string config "env") "production")}))

;; ----------------------------------------------------------------------------
(defn- extract-key
  "Helper to extract a key from either a managed reference
  to a config or an actual immutable config."
  [key]
  (fn [config]
    ((if (instance? IDeref config)
       (deref config)
       config)
     key)))

;; ----------------------------------------------------------------------------
(def ^{:doc "The port on which to listen for http requests."}
  server-port (extract-key :server-port))

emccue05:06:58

so you can pass around a config as is given by load-from-classpath

emccue05:06:07

or you can pass around a config within an atom

emccue05:06:25

the code reading it is generic over both cases

emccue05:06:47

and if you pass an atom you can have a background thread reloading the config every now and then or something

emccue05:06:38

so whatever parts of the config that aren't just read at startup can be altered without a restart (noting that in general avoiding a restart while changing something like server-port would be pretty darn hard)

emccue05:06:33

sorry for all the small messages, i'm from the texting generation

emccue05:06:13

also missed the part about you not wanting it to be stateful - don't have a good answer for you there

emccue05:06:33

if you want to mock out a global in some way it needs to either be a parameter and passed to functions

emccue05:06:44

or it ideally would be a dynamic var you could rebind

emccue05:06:25

(def ^{:dynamic true} *config* (load))

(defn server-port [] (:server-port *config*))

;; And then in your tests
(binding [*config* (load-differently)]
  (... (is (= (config/server-port) 8000))))

wombawomba12:06:29

Looking for suggestions on how to do server-sent events and HTTP/2 in Clojure. Currently I’m using Ring with Reitit, so ideally I’d like to use something that’s compatible with that stack 🙂

wombawomba12:06:17

…or maybe I should just go with WebSocket?

markaddleman13:06:17

I installed clojure tools from brew. Lately, I've been getting the following warning message:

Warning: Calling 'devel' blocks in formulae is deprecated! Use 'head' blocks or @-versioned formulae instead.
Please report this issue to the clojure/tools tap (not Homebrew/brew or Homebrew/core), or even better, submit a PR to fix it:
  /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/clojure/homebrew-tools/Formula/clojure.rb:7

Alex Miller (Clojure team)14:06:17

this is due to a recent change in brew, we are aware

👍 3
borkdude14:06:51

it was established in previous discussions that this was undefined behavior in destructuring:

clj -e '(let [{:keys [a b] :or {b (inc a)}} {:a 1}] b)'
2
i.e. the pattern of using previously destructured bindings in defaults.

borkdude14:06:05

but what about this one:

(defn- a [] 1)
(let [{:keys [a] :or {a a}} {}] (a))'
I'm not suggesting one should do this, but this was reported at clj-kondo (clj-kondo thinks the var a is unused)

Alex Miller (Clojure team)15:06:27

behavior of :or bindings that depend on symbols bound in the destructuring is undefined

borkdude15:06:35

in this case it depends on the var, which happens to have the same name as a binding

borkdude15:06:59

but since it's not clear without looking at the implementation what this does, it might also be better to mark it as undefined?

Alex Miller (Clojure team)15:06:53

yeah, sorry I actually skipped over the def entirely

borkdude15:06:30

still agree with undefined tho?

Alex Miller (Clojure team)15:06:01

the semantics of :or are to provide default values that will be bound to the new local binding in the case where the key is not found in the map. because the local has not yet been bound at the point where the :or value is evaluated, I think it is reasonable to expect this to refer to the var a in this case.

borkdude15:06:21

I'll re-open the issue and fix it then

Alex Miller (Clojure team)15:06:24

I don't think this is particularly good code as it's hard to understand, so I would probably still say don't do this :)

borkdude14:06:53

it's kind of ambiguous what a is referring to: the previous binding or not

borkdude14:06:14

(I know the REPL output, it's the var that gets bound)

noisesmith14:06:52

given that {} is unordered, and the :or could be executed before or after the :keys, I find that surprising

noisesmith14:06:18

but of course that's a bad mental model for the compiler here, and the real solution is just "don't do that"

ivana15:06:30

(clojure.pprint/pprint
   (macroexpand
    '(let [{:keys [a b] :or {b (inc a)}} {:a 1}]
       b)))

noisesmith15:06:40

I guess the right mental model is that :or always refers to the local binding in the same destructure form

ivana15:06:49

dont forget that let is a macro

ivana15:06:51

(let* [map__210260 {:a 1}
         map__210260 (if
                      (clojure.core/seq? map__210260)
                       (clojure.lang.PersistentHashMap/create
                        (clojure.core/seq map__210260))
                       map__210260)
         a (clojure.core/get map__210260 :a)
         b (clojure.core/get map__210260 :b (inc a))]
        b)

borkdude15:06:49

@ivana geez, I almost forgot let was a macro thanks 😉. But also don't forget that maps are unordered.

ivana15:06:26

Yep. And I also agree with "dont do that" 🙂

noisesmith15:06:48

right - the destructure code ends up accessing the map by keys in a specific order, rather than consuming the map in the order of seq

borkdude15:06:42

one could argue that if one uses a default name equal to the binding, the code will guarantee that it's not one of the previous bindings, but one really has to dig into the code to see it

ivana15:06:59

there can be imagined a lot of cases, when you have to dig into the code to see the behaviour, or even there is UB. and it seems, that it can be as UB, and "dont do that" case. anyway you have dozen way to rewrite it in more readable way, f.e.

(let [{:keys [a b]} {:a 1}
        b (or b (inc a))]
    b)

borkdude15:06:47

I think these discussions are giving rise to a linter warning: https://github.com/borkdude/clj-kondo/issues/916

ivana15:06:12

And there can be, that in next clojure core release :or destructuring can be refactored and the result can be different. I can show you one more funny case with different results using ->> and as-> macro as well, if you want 🙂

borkdude15:06:52

more than welcome to post such examples in the issue I just posted 🙂

ivana15:06:22

It is not related to :or case, so it is the example of general unintuitive behavior at all 🙂

walterl15:06:02

Can anyone recommend a linter (primarily clj, but cljs too would be a bonus) that can be configured to fail on usage of specific functions (e.g. clojure.java.shell/sh)? It should be able to identify these vars regardless of :as alias or being :refer-red.

walterl15:06:37

I've started one based on rewrite-clj, but this feels like something that should already exist.

borkdude15:06:00

@clojurians-slack100 you can use clj-kondo's analysis output for this

❤️ 3
borkdude15:06:34

it can lint clj, cljs and cljc

borkdude15:06:27

together with babashka you can write a very fast script from this.

walterl15:06:52

I'll have a look and probably come annoy you with further questions 😉

walterl15:06:08

(I'm an avid babashka user 🙂)

isak16:06:26

If I start a core.async channel that loops for a long time, but don't keep a reference to the channel, can it get GCed and stop?

Alex Miller (Clojure team)16:06:59

channels don't loop, so what's looping?

Alex Miller (Clojure team)16:06:24

so it has a reference to the channel

Alex Miller (Clojure team)16:06:04

if your go loop ends and nothing else refers to the channel, then it will be gc'ed

6
dpsutton20:06:15

to confirm, we can set (System/setProperty "clojure.main.report" "stderr") to get stacktraces written to stderr rather than a temp file?

dpsutton20:06:37

makes our logs from jobs in the cloud not very helpful

dpsutton20:06:53

but setting this system property will change this behavior, yes?

dpsutton20:06:09

(making sure it doesn't need to be set on jvm startup or anything)

dpsutton20:06:51

and if trying to get this in through lein is it lein update-in :jvm-opts conj '"-Dclojure.main.report=stderr"' -- repl?

noisesmith20:06:34

wouldn't it be easier to update the jvm-opts for your task in project.clj, or use an uberjar to run your repl instead of lein?

dpsutton20:06:43

kinda. this is in a bit of code that is comparing across git versions so any changes i add to project.clj or code don't get seen

dpsutton20:06:49

until that change makes its way onto master

joshkh21:06:14

i'm trying to test the class of an object (= datomic.client.impl.shared.Db (class my-db)), but i get a ClassNotFound exception for datomic.client.impl.shared.Db despite having the datomic.client.api library required in my project. i've also tried :import ing the class directly. where am i going wrong?

hiredman21:06:39

the class is likely generated by loading clojure code, if you haven't loaded the clojure code that creates it, then it won't exist

hiredman21:06:55

(a deftype or defrecord)

joshkh22:06:15

bingo. Db is the result of deftype. thanks @hiredman, i required its parent namespace which solved my problem.

ghadi22:06:56

you doing something with spec @joshkh ?

ghadi22:06:30

if so, might be more robust to check for an interface/protocol rather than concrete class

joshkh22:06:03

not quite, i'm working on serialising function arguments for a k/v cache, however checking concrete classes has been a pain. are you referring to instance? rather than class?

ghadi22:06:54

(supers (class your-db)) <- instance checking on one of the interfaces in there

👍 3
joshkh22:06:33

that's exactly what i was looking for. not just for "edge case" classes, but also for checking when objects fall in the bucket of native clojure data structures. thanks a lot. 🙂

noisesmith22:06:13

yeah, instance? is what you want for that

(ins)user=> (instance? java.util.Map {})
true
(ins)user=> (instance? clojure.lang.IObj {})
true

👍 3
joshkh22:06:10

cool. i nailed down IObj for testing for metadata support. sounds like i'm heading in the right direction.