clojure

jyn 2026-05-10T07:30:06.074009Z

is there a way in a macro to say "put this in a () syntax quote if it's not nil`, otherwise leave the quote untouched"?

jyn 2026-05-10T07:31:00.969449Z

right now i have (defn ~name ~doc-string ~attr-map ~argspec ~body), which works if all those args are non-nil, but breaks if attr-map` is nil. and then i tried ~@attr-map, which works if it is nil, but doesn't work if it's non-nil.

phronmophobic 2026-05-10T07:34:49.736549Z

I would probably just construct a list using seq functions. Something like:

(apply list
       (remove nil?
               [`defn
                doc-string
                attr-map
                argspec
                body]))

šŸ‘ 1
šŸ˜ 1
jyn 2026-05-10T07:36:00.346449Z

that worked like a charm, thank you!

jyn 2026-05-10T08:09:25.294829Z

I am trying to test this macro. (perhaps I have spent too much time on this ...) I wrote the following test:

(defmacro test-defcompute
  [expected form]
  (prn form)
  (prn (macroexpand form))
  `(is (= '~expected (macroexpand ~form))))

(deftest defcompute
  (testing "Macro expansion of compute-node helper"
    (test-defcompute (defn f [x]) (g/defcompute f [x] x))))
That prints the following:
FAIL in (defcompute) (graphcom_test.cljc:94)
Macro expansion of compute-node helper
expected: (clojure.core/= (quote (defn f [x])) (clojure.core/macroexpand (g/defcompute f [x] x)))
  actual: (not (clojure.core/= (defn f [x]) #'bortexz.graphcom-test/f))
I think it's trying to compare the expected tokens against the actual evaluated definition? I wanted to compare the actual tokens, before they've been evaluated. I tried just changing it to (macroexpand '~form) but that doesn't actually expand the macro, it just gives back the original tokens pre-expansion.

phronmophobic 2026-05-11T08:30:34.750969Z

I’m a little surprised macroexpand is showing up in the stack trace. that makes me think something might be using stale code

phronmophobic 2026-05-11T08:31:18.188179Z

I thought the latest version was just testing a regular function (that will be used by the macro) and not the macro itself

jyn 2026-05-11T08:32:38.121829Z

the regular function is calling macroexpand

šŸ‘ 1
jyn 2026-05-11T08:33:58.027219Z

(defn- defcompute*
  ([name & tail]
  (let [[body tail] [(last tail) (butlast tail)]
        [argspec optional-args] [(last tail) (butlast tail)]
        [doc-string attr-map] optional-args
        [destructured-params destructured-body] (-> `(fn ~argspec ~body) macroexpand rest first)
        destructured-params-by-name (concat destructured-params [:as a])
        input-map (into {} (for [sym destructured-params]
                              [(keyword sym) sym]))
        fn-params ['state {:keys destructured-params}]
        body `(g/compute-node
                ~input-map
                (fn ~fn-params ~destructured-body))]
    (print argspec destructured-params)
    (apply list
            (remove nil?
                    ['defn name doc-string attr-map argspec body]))))))

(defmacro defcompute [& args]
  "Accepts the same syntax as `defn`.
  Defines a function that returns a compute node whose body is the last macro argument."
  (apply defcompute* args)))

2026-05-11T10:23:59.480139Z

@jyn514 maybe it is the slack formatting but I think the body of your macro should be ` (defcompute* ~@args) Normally you don't have to use apply in macros

phronmophobic 2026-05-11T10:35:25.245829Z

I think you do in this case. defcompute* returns the resulting code and needs to be run at compile time rather than runtime.

šŸ‘ 1
phronmophobic 2026-05-11T10:41:20.664759Z

I assume this code is in a cljc file? When compiling clojurescript, I believe the macroexpansion happens using jvm code. I believe you are running into Gotcha #5, https://code.thheller.com/blog/shadow-cljs/2019/10/12/clojurescript-macros.html#gotcha-5-cljc. I think the problem is that macroexpand is a macro when used in cljs code. I don't think you actually need defcompute* at runtime, so moving defcompute* to a separate clj file might be easiest.

phronmophobic 2026-05-11T10:42:10.645289Z

It might be worth asking in #clojurescript to get some cljc experts opinions.

phronmophobic 2026-05-11T10:43:01.925669Z

As an additional thought, rather than macroexpand, you may be interested in clojure core's little known destructure, https://clojure.org/guides/destructuring#_macros.

phronmophobic 2026-05-11T10:45:24.158239Z

Actually, following the step by step section at the top of the clojurescript macros blog post might solve your problem.

jyn 2026-05-10T08:10:44.353119Z

maybe i should just make this a function that accepts lists of tokens ...

phronmophobic 2026-05-10T08:14:39.468429Z

I've started writing my macros so all the logic is in a helper function:

(defn mymacro* [args]
  `(+ 42 ~@args))

(defmacro mymacro [& args]
  (mymacro* args))
You can then test mymacro*.

jyn 2026-05-10T08:15:50.153159Z

ohhh i forgot that macros in clojure are just regular functions

jyn 2026-05-10T08:16:05.811219Z

so the only thing special about defmacro is it automatically quotes its arguments and then unquotes the return value?

phronmophobic 2026-05-10T08:21:11.405369Z

I would say that defmacro just marks the var's metadata as a macro, https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Var.java. When the compiler evaluates some code, the macro function is called with the code as data.

phronmophobic 2026-05-10T08:21:53.151149Z

just like you can call (eval (list '+ 1 2)) ;; 3

phronmophobic 2026-05-10T08:22:25.669249Z

if + were a macro, it would get called with the unevaluated syntax as data.

jyn 2026-05-10T08:22:38.974859Z

right, that makes sense

phronmophobic 2026-05-10T08:23:07.532689Z

but yea, macros are just functions that get called at compile time.

jyn 2026-05-10T08:23:51.962389Z

yay this worked!

(defn test-defcompute
  [expected form]
  (is (= expected (apply #'g/defcompute* form))))

(deftest defcompute
  (testing "Macro expansion of compute-node helper"
    (test-defcompute '(defn f [x]) '(f [x] x))))

šŸŽ‰ 1
jyn 2026-05-10T08:33:44.425879Z

hm but this fails when run in clj-js for some reason ...

jyn 2026-05-10T08:34:16.317369Z

do you have any idea what this error could mean?

Running task for: test, cljs-test
#error {
 :cause Assert failed: Argument to macroexpand must be quoted
(core/= (core/first quoted) (quote quote))
 :via
 [{:type clojure.lang.ExceptionInfo
   :message failed compiling file:/Users/jyn/src/graphcom/src/bortexz/graphcom.cljc
   :data {:file #object[java.io.File 0x3203ffa3 /Users/jyn/src/graphcom/src/bortexz/graphcom.cljc], :clojure.error/phase :compilation}
   :at [cljs.compiler$compile_file$fn__3895 invoke compiler.cljc 1724]}
  {:type clojure.lang.ExceptionInfo
   :message nil
   :data #:clojure.error{:source /Users/jyn/src/graphcom/src/bortexz/graphcom.cljc, :line 148, :column 50, :phase :macroexpansion, :symbol cljs.core/macroexpand}
   :at [cljs.analyzer$macroexpand_1_STAR_$fn__2622 invoke analyzer.cljc 3923]}
  {:type java.lang.AssertionError
   :message Assert failed: Argument to macroexpand must be quoted
(core/= (core/first quoted) (quote quote))
   :at [cljs.core$macroexpand invokeStatic core.cljc 3093]}]
 :trace
 [[cljs.core$macroexpand invokeStatic core.cljc 3093]

2026-05-11T00:36:29.106839Z

cljs is a nightmare lol

2026-05-11T00:36:41.017609Z

i ran into something similar recently

2026-05-11T00:37:25.351619Z

i found it best to not test the macroexpansion in cljs

🄲 1
jyn 2026-05-10T04:00:16.476739Z

I want to write a macro that wraps defn. Is there an easy way to match all the parameters of defn? I know there's at least the params, doc-string, and attr-map ... ideally i would just forward most things directly through to defn.

2026-05-11T00:33:59.040149Z

i can help, i've done this many times

oyakushev 2026-05-12T12:03:35.629669Z

You can use Spec to parse arguments structurally, and then use unform to turn it back into syntax. See :clojure.core.specs.alpha/defn-args

jyn 2026-05-10T04:01:06.719239Z

alternatively, is there a version of defn that's a function and not a macro?

jyn 2026-05-10T04:08:22.488319Z

ah hm, i guess now i need to parse the params myself ... is there some helper function for that?

jyn 2026-05-10T04:12:22.915109Z

i wonder if i want macroexpand-1 or something like that

jyn 2026-05-10T04:14:27.049039Z

ok yeah this totally works

user=> (macroexpand '(fn [y {:keys [z] :as m}] y))
(fn* ([y p__3730] (clojure.core/let [{:keys [z], :as m} p__3730] y)))

jyn 2026-05-10T04:44:32.517839Z

is there a way in Conjure to see the full stack trace for an error?

Olical 2026-05-10T08:48:27.680559Z

There is <prefix>ve for "view error"

Olical 2026-05-10T08:49:05.358349Z

There's also v1 v2 etc for the other nREPL magic vars. May be others I'm forgetting but they're in the help files for the Clojure client.

jyn 2026-05-10T09:06:56.008659Z

@olical do you mean this inline highlight? that gets cut off and doesn't show me the stack trace at all.

jyn 2026-05-10T09:07:47.593619Z

right i see, it's put the full thing in the log where i can see it all

jyn 2026-05-10T09:08:07.752369Z

that still has the original problem though, it's printed as a host traceback instead pretty printing the SCI traceback

Olical 2026-05-10T09:08:30.589789Z

Hm, I think it used to and should. But this could be a babashka specific thing, we've had issues with pprint not working there in the past through the nREPL integration in a way where I could never get a good repro for bb so I could never be sure where the fault was.

Olical 2026-05-10T09:08:50.035759Z

Oh wait, ve and then check the log, not online?

šŸ‘ 1
jyn 2026-05-10T09:08:51.434799Z

if it helps, i have an SCI pretty-printer for flower that you could borrow from

Olical 2026-05-10T09:08:53.385909Z

Inline*

jyn 2026-05-10T09:09:33.909469Z

this is the error i'm seeing. all the info is there, it's just very hard to understand. https://clojurians.slack.com/archives/C03S1KBA2/p1778389096127689?thread_ts=1778388272.517839&amp;cid=C03S1KBA2

Olical 2026-05-10T09:10:46.924239Z

Are you calling print-stack-trace or is this an internal failure within some error handling code I wonder?

jyn 2026-05-10T09:11:04.711679Z

internal failure i believe

jyn 2026-05-10T09:11:07.641339Z

i'm just calling *e

Olical 2026-05-10T09:22:53.445939Z

So if ve has issues in your particular setup that could do with fixing, especially if Conjure is trying to call things to get more error info and it's failing. Not 100% sure if that's the case though. I've always used ve and although it's data and not human formatted I've found it the most thorough way to understand what happened and where. I think there's now some nicer error formatting functions in full JVM Clojure (unsure of the other implementations) but those will hide some info, so might sometimes obscure the issue. So does *e with pprint show something very different to what Conjure prints with it's mapping

Olical 2026-05-10T09:23:06.073459Z

Because if so, definitely a bug and we can get that fixed.

jyn 2026-05-10T04:44:54.952519Z

right now i just see this:

; (err) java.lang.Exception: Cannot call defquery with 2 arguments dev.jyn.flower.main /Users/jyn/src/flower2/src/dev/jyn/flower/main.clj:2:24
but it's missing any of the context that would make it useful ...

jyn 2026-05-10T04:49:07.285269Z

i got it to evaluate ((requiring-resolve 'clojure.stacktrace/print-cause-trace) *e) but this is a babashka environment so the stacktrace kinda sucks šŸ˜• is there a pretty-printer for :sci/error somewhere?

Steven Lombardi 2026-05-10T04:57:16.047389Z

Assuming you're talking about the most recent uncaught exception, does just typing *e not give you what you need?

jyn 2026-05-10T04:58:16.127689Z

it does not, no.

; eval (root-form): *e
#error {
 :cause "Unable to resolve symbol: st/print-stack-trace"
 :data {:type :sci/error, :line 1, :column 1, :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj", :phase "analysis"}
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "Unable to resolve symbol: st/print-stack-trace"
   :data {:type :sci/error, :line 1, :column 1, :message "Unable to resolve symbol: st/print-stack-trace", :sci.impl/callstack #object[clojure.lang.Volatile 0x17b13efb {:status :ready, :val ({:line 1, :column 1, :ns #object[sci.lang.Namespace 0xf63cd38 "dev.jyn.flower.main"], :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj"} {:line 1, :column 1, :ns #object[sci.lang.Namespace 0xf63cd38 "dev.jyn.flower.main"], :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj", :sci.impl/f-meta {:ns #object[sci.lang.Namespace 0x533a850a "clojure.core"], :name defn, :file "clojure/core.clj", :column 1, :sci/built-in true, :line 285, :macro true, :arglists ([name doc-string? attr-map? [params*] prepost-map? body] [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?]), :doc "Same as (def name (fn [params* ] exprs*)) or (def\n    name (fn ([params* ] exprs*)+)) with any doc-string or attrs added\n    to the var metadata. prepost-map defines a map with optional keys\n    :pre and :post that contain collections of pre or post conditions."}} {:line 1, :column 1, :ns #object[sci.lang.Namespace 0xf63cd38 "dev.jyn.flower.main"], :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj"})}], :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj", :phase "analysis"}
   :at [sci.impl.utils$rethrow_with_location_of_node invokeStatic "utils.cljc" 142]}
  {:type clojure.lang.ExceptionInfo
   :message "Unable to resolve symbol: st/print-stack-trace"
   :data {:type :sci/error, :line 1, :column 1, :file "/Users/jyn/src/flower2/src/dev/jyn/flower/main.clj", :phase "analysis"}
   :at [sci.impl.utils$throw_error_with_location invokeStatic "utils.cljc" 49]}]
 :trace
 [[sci.impl.utils$throw_error_with_location invokeStatic "utils.cljc" 49]
  [sci.impl.resolve$throw_error_with_location invokeStatic "resolve.cljc" 11]
  [sci.impl.resolve$resolve_symbol invokeStatic "resolve.cljc" 289]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 1989]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 1986]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze_children_tail$fn__4307 invoke "analyzer.cljc" 59]
  [clojure.core$mapv$fn__8569 invoke "core.clj" 7059]
  [clojure.lang.PersistentVector$ChunkedSeq reduce "PersistentVector.java" 549]
  [clojure.core$reduce invokeStatic "core.clj" 6964]
  [clojure.core$mapv invokeStatic "core.clj" 7050]
  [sci.impl.analyzer$analyze_children_tail invokeStatic "analyzer.cljc" 56]
  [sci.impl.analyzer$return_do invokeStatic "analyzer.cljc" 68]
  [sci.impl.analyzer$expand_fn_args_PLUS_body invokeStatic "analyzer.cljc" 304]
  [sci.impl.analyzer$analyze_fn_STAR_$fn__4523 invoke "analyzer.cljc" 399]
  [clojure.lang.PersistentList reduce "PersistentList.java" 143]
  [clojure.core$reduce invokeStatic "core.clj" 6964]
  [sci.impl.analyzer$analyze_fn_STAR_ invokeStatic "analyzer.cljc" 397]
  [sci.impl.analyzer$dispatch_special invokeStatic "analyzer.cljc" 1573]
  [sci.impl.analyzer$analyze_call invokeStatic "analyzer.cljc" 1759]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 2022]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze_call invokeStatic "analyzer.cljc" 1781]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 2022]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 1986]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze_def invokeStatic "analyzer.cljc" 750]
  [sci.impl.analyzer$dispatch_special invokeStatic "analyzer.cljc" 1573]
  [sci.impl.analyzer$analyze_call invokeStatic "analyzer.cljc" 1759]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 2022]
  [sci.impl.analyzer$analyze invoke "analyzer.cljc" 1984]
  [sci.impl.analyzer$analyze_call invokeStatic "analyzer.cljc" 1781]
  [sci.impl.analyzer$analyze invokeStatic "analyzer.cljc" 2022]
  [sci.impl.interpreter$eval_form_STAR_ invokeStatic "interpreter.cljc" 23]
  [sci.impl.interpreter$eval_form invokeStatic "interpreter.cljc" 62]
  [sci.core$eval_form invokeStatic "core.cljc" 355]
  [babashka.nrepl.impl.server$eval_msg$fn__25699$fn__25700 invoke "server.clj" 109]
  [babashka.nrepl.impl.server$eval_msg$fn__25699 invoke "server.clj" 105]
  [babashka.nrepl.impl.server$eval_msg invokeStatic "server.clj" 94]
  [babashka.nrepl.impl.server$fn__25757 invokeStatic "server.clj" 266]
  [babashka.nrepl.impl.server$fn__25757 invoke "server.clj" 266]
  [clojure.lang.MultiFn invoke "MultiFn.java" 239]
  [babashka.nrepl.server.middleware$default_process_msg invokeStatic "middleware.clj" 13]
  [babashka.nrepl.server.middleware$wrap_process_message$fn__25826 invoke "middleware.clj" 31]
  [clojure.core$completing$fn__8562 invoke "core.clj" 7010]
  [clojure.core$map$fn__5952$fn__5953 invoke "core.clj" 2759]
  [babashka.nrepl.impl.server$session_loop invokeStatic "server.clj" 342]
  [babashka.nrepl.impl.server$listen$fn__25811 invoke "server.clj" 363]
  [sci.impl.vars$binding_conveyor_fn$fn__432 invoke "vars.cljc" 129]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread runWith "Thread.java" 1487]
  [java.lang.Thread run "Thread.java" 1474]
  [com.oracle.svm.core.thread.PlatformThreads threadStartRoutine "PlatformThreads.java" 832]
  [com.oracle.svm.core.thread.PlatformThreads threadStartRoutine "PlatformThreads.java" 808]]}

Steven Lombardi 2026-05-10T04:59:29.400929Z

That looks like a new issue. Your resolve failed it seems? Trigger the original exception and try *e again.

jyn 2026-05-10T05:00:12.397909Z

yes, i'm aware that e is different now. my point is that *e has *two traces here, the JVM trace and the SCI trace, and the SCI trace is very hard to read because it's all collapsed on one line.

Steven Lombardi 2026-05-10T05:01:55.449869Z

If it's valid edn could you pipe it to the Clojure pretty printer?

jyn 2026-05-10T05:02:53.503009Z

you mean clojure.pprint? that will print it as a data structure, but i don't want it as a data structure, i want it printed pretty just like a JVM trace would be

jyn 2026-05-10T05:03:11.369829Z

i don't want to see :line 1 :column 1, it's just noise

Steven Lombardi 2026-05-10T05:06:58.361539Z

I think doing it as a JVM trace will be possible but more involved. Not 100% sure. But I think the edn representation will get you 80 - 90% of the readability of the JVM trace without as much work.

Steven Lombardi 2026-05-10T05:07:59.878649Z

And yeah I was referring to pprint

jyn 2026-05-10T05:08:18.142609Z

i think i'm going to use JVM clojure for my repl, bb nrepl-server doesn't support deps.edn either

jyn 2026-05-10T05:08:22.653809Z

and then it's a moot point

Steven Lombardi 2026-05-10T05:09:23.449159Z

Might be worth asking in #babashka since this seems specific to that flavor.

šŸ‘ 1
jyn 2026-05-10T05:10:44.637639Z

this seems like something that would be nice to ship with Conjure by default, since Conjure defaults to bb nrepl-server