Fork me on GitHub
#beginners
<
2023-09-20
>
Nimit Maru01:09:43

Hi all. I am getting an "Attempting to call unbound fn" error on this code running in a JAR (it runs fine in dev):

(defn handle-ws-connection [req]
  (let [doc-uuid (uuid-from-ws-req req)]
    (hk-server/as-channel ...more code... )))

(defmethod ig/init-key :adapter/ws-http-handler [_ {:keys [handler] :as opts}]
  (hk-server/run-server
   (fn [req]
     (if (...some code here...)
       (handle-ws-connection req)
       (handler req)))
   (-> opts (dissoc :handler) (assoc :join? false))))
The code above results in the error: Attempting to call unbound fn: #'calcnotes.server.main/handle-ws-connection. Why would the handle-ws-connection be unbound when it is defined above, in the same file?

Ingy döt Net04:09:06

I was pleased when I learned that:

(->> maps (remove #(= (:SKIP %) true)))
collapses to:
(->> maps (remove :SKIP))
so much to learn!

💯 1
🎉 2
Ingy döt Net04:09:06

Just realized they are not the same. But works for my use case, so still pleased 🙂

seancorfield05:09:43

(remove (comp true? :SKIP) maps)

1
phill10:09:26

It was at exactly the moment when I made such a discovery (while drooling on The Joy Of Clojure in the bay window of a shawarma shop) that I fell off the stool laughing and was hooked.

🪝 1
💘 1
❤️ 1
Mateusz Mazurczak13:09:18

How to pass variadic args from macro as one argument? So if I have macro

(defn do-smth [sequence])

(defmacro first [& args]
`(do-smth ~@args))
If I do it like that it will pass args to do-smth as multiple arguments. Is there a way to pass those args as a sequence to defn?

Bob B13:09:38

two potential short answers - re-list them or quote the list:

(defn do-smth [sequence] sequence)
(defmacro first1 [& args]
  `(do-smth '~args))
(defmacro first2 [& args]
  `(do-smth (list ~@args)))
(first1 1 2 3)
;=> (1 2 3)
(first2 1 2 3)
;=> (1 2 3)
while the splice could be removed, that would pass an unquoted list, which means it'll be treated as a function call that gets evaluated as a parameter to the fn. Quoting means the list won't be evaluated as a function parameter (which might or might not be what you want), and unsplicing the args into a list call will build the list (which will evaluate function calls within the variadic args:
(first1 5 6 (+ 1 2))
;=> (5 6 (+ 1 2))
(first2 5 6 (+ 1 2))
;=> (5 6 3)

❤️ 1
hairfire13:09:12

Will this do what you need? (defmacro first [& args] `(do-smth [~@agrs]))

Isaac Ballone15:09:02

Is there a better way to write this core.async code? If either go blocks fail, the result is nil, but if the clojure.core.async/map fails, it blocks forever. I could wrap it a try-catch, but I was curious if there's a more idiomatic way to approach this. For context, I want to kick off two HTTP requests in parallel and then process the results

(a/<!!
 (a/map (fn [x y]
          (throw (Exception. "Fail"))
          (+ x y))
        [(a/go 1)
         (a/go 2)]))
=> ;; blocks forever

Isaac Ballone15:09:27

I guess I could just shove the values into a vec, and then use them after

(let [ch (a/map vector [(a/go 1) (a/go 2)])
      [x y] (a/<!! ch)]
  (throw (Exception. "Fail"))
  (+ x y))

stopa15:09:34

Hey team, are there naming conventions for constants in clojure? i.e:

(def default-paths {:a "./foo/bar" :b "./bar/baz"})
It can be hard to tell what is a constant when used in other functions. I was thinking uppercasing: DEFAULT-PATHS, or DEFAULT_PATHS

jpmonettas15:09:35

there isn't since almost everything is constant (immutable), you would probably end up with everything in upper case. I have seen it the other way around, some naming conventions for refs, like *my-ref

dpsutton15:09:45

every def is a constant

dpsutton15:09:13

feel free to make your own conventions though of course. that’s how things become conventions in the wild. people try something and others like it

Noah Bogart15:09:40

i tend to see ALL-CAPS as a way to denote global vars that should be considered "constants"

stopa15:09:06

Thanks team!

stopa21:09:21

Hey ya’ll does anyone have experience with Opentelemtry Java / Honeycomb in clojure? I’m playing with instrumenting it now. Things work but I am not sure I fully understand how spans link together. Do you know of any articles / example code that shows use in clojure?

jkrasnay21:09:21

I’ve been working on exactly that recently. I have something like this:

(def ^:dynamic *span* nil)

(defn new-span
  [span-name]
  (when @tracer
    (-> @tracer
        (.spanBuilder (name span-name))
        (cond->
          *span* (.setParent (-> (Context/current)
                                 (.with *span*)))
          (not *span*) .setNoParent)
        .startSpan)))

(defn end-span
  []
  (when *span*
    (.end *span*)))

(defmacro span
  [span-name & body]
  `(binding [*span* (new-span ~span-name)]
     (try
       (do ~@body)
       (catch Throwable t#
         (.recordException *span* t#)
         (throw t#))
       (finally
         (end-span)))))

stopa15:09:30

Love this! Thank you @U0DTSCAUU

👍 1
Sahil Dhanju23:09:52

I've written quite a dense function with nested lets and when invoking it the first time I'm getting a ClassCastExpection in the REPL. The exception doesn't tell me much about where the cast is occurring or what symbols are involved. What's the recommended workflow of debugging this? I'm using Cursive. *e doesn't have much either

hiredman23:09:49

write less dense functions

Sahil Dhanju23:09:29

my function looks as dense as many of the library functions I've been reading so I imagined there was some kind of workflow/method for this

dpsutton23:09:45

if your function is defined in code and required you’ll have better line information. write your code in a source buffer, (require <the-namespace> :reload) to reload the code, and then call it and you should have stack traces with line numbers on them so you know where the error is occurring

🙌 1
hiredman23:09:41

if it is too dense for you to debug effectively, it is too dense

☝️ 1
1
dpsutton23:09:58

but in general, his advice is solid. It’s fun to codegolf and feel clever. But it gets quite tedious to try to figure out where it goes wrong. Writing less clever code can greatly ease in debugging

Sahil Dhanju23:09:52

@U11BV7MTK that worked! Thanks! I say "dense" with a relative context - dense for me (a beginner). It's function with a let, a filter and then a let inside that filter which ought to be simple enough. I've seen denser functions in clojuredocs

dpsutton23:09:40

i have a hotkey to (require <current-namespace> :reload) and use that almost exclusively. if you eval things individually there are no line numbers so you’re left guessing

✍️ 2
dpsutton23:09:55

same with source, doc, and other niceities

Jason Bullers23:09:45

Hmmm interesting. Do you know why that's so? I would have thought evaluating functions as I go wouldn't be any different

Bob B23:09:47

as an alternative for consideration, Stuart Halloway has talked about an example that might be useful: <https://vimeo.com/223309989#t=1790s>

👀 1
dpsutton23:09:41

the repl can’t really track line numbers. if you enter (+ 1 1) that’s just kinda line 1 right? So evaling stuff can’t really have line numbers. When you require a namespace, it goes to the file that specifies it and it keeps track of line numbers as it goes through each form in the file

👍 1
dpsutton23:09:55

i sent the divide-and-throw function to the repl. and the line number is REPL:189 which isn’t super helpful

core=> (defn divide-and-throw [x]
         (/ x 0))
#'metabase.automagic-dashboards.core/divide-and-throw
core=> (divide-and-throw 3)
Execution error (ArithmeticException) at metabase.automagic-dashboards.core/divide-and-throw (REPL:189).
Divide by zero
if instead i require the namespace i defined it in and then eval a form to call it, my error message can tell me where in the file it occurred:
core=> (divide-and-throw 3)
Execution error (ArithmeticException) at metabase.automagic-dashboards.core/divide-and-throw (core.clj:1724).
Divide by zero
on line 1724

gratitude-thank-you 2
Sahil Dhanju23:09:56

That makes a lot of sense, thanks again! Would have been completely in the dark since I simply didn't have the line number

seancorfield00:09:05

Following up on this advice, I don't ever type into a REPL. I put scratch code in a file - usually in a (comment ...) form and use my editor integration to eval it. If you can get used to a workflow like that, you'll find working with Clojure becomes a lot easier.

Jason Bullers00:09:53

That's typically what I do, and it's a fantastic experience. I was so used to just evaluating things like that, I never considered reloading namespaces instead of just evaluating functions. I've definitely hit the line numbers problem, and never realized there's such a simple solution. Live coding just got a level up 🙂

dpsutton00:09:26

yeah. people might not realize that evaluating the form without typing it into the repl actually sends it to the repl

Jason Bullers00:09:22

I feel like I knew that's how it worked, but somehow it never really clicked that line numbers are a consequences of loading a namespace and so you wouldn't get them by piecewise building up a namespace by sending forms to the repl

Jason Bullers00:09:48

Thanks for spelling that out 👍:skin-tone-2:

jpmonettas01:09:39

@U05N8AJMWQG shameless plug here, but have you seen http://www.flow-storm.org/ ? If I type some contrived one liner like this :

user=> (let [coll [0 "1"] v [:first :second] a (let [b (->> coll (map (fn [n] (v n))))] b)] a)
...
Error printing return value (IllegalArgumentException) at clojure.lang.APersistentVector/null (APersistentVector.java:297).
Key must be integer
I get that exception which doesn't contain any lines. But if in a FlowStorm repl I just evaluate :ex right after so it moves the debugger into the exception where I see like in the image below, which I find much easier. And you also get to step around.

Sahil Dhanju08:09:16

I have not seen FlowStorm, it looks insanely powerful. I'll check it out!

phill21:09:57

OP mentioned filter & unilluminating *e. That combination can result from lazy sequences. A debugging technique is to wrap a suspect (filter...), (map...), etc., in (doall...) to force the whole sequence to be realized then-and-there.

seancorfield22:09:47

Or use filterv and mapv (eager, producing vectors).