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"?
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.
I would probably just construct a list using seq functions. Something like:
(apply list
(remove nil?
[`defn
doc-string
attr-map
argspec
body]))that worked like a charm, thank you!
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.Iām a little surprised macroexpand is showing up in the stack trace. that makes me think something might be using stale code
I thought the latest version was just testing a regular function (that will be used by the macro) and not the macro itself
the regular function is calling macroexpand
(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)))@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
I think you do in this case. defcompute* returns the resulting code and needs to be run at compile time rather than runtime.
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.
It might be worth asking in #clojurescript to get some cljc experts opinions.
As an additional thought, rather than macroexpand, you may be interested in clojure core's little known destructure, https://clojure.org/guides/destructuring#_macros.
Actually, following the step by step section at the top of the clojurescript macros blog post might solve your problem.
maybe i should just make this a function that accepts lists of tokens ...
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*.ohhh i forgot that macros in clojure are just regular functions
so the only thing special about defmacro is it automatically quotes its arguments and then unquotes the return value?
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.
just like you can call (eval (list '+ 1 2)) ;; 3
if + were a macro, it would get called with the unevaluated syntax as data.
right, that makes sense
but yea, macros are just functions that get called at compile time.
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))))hm but this fails when run in clj-js for some reason ...
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]cljs is a nightmare lol
i ran into something similar recently
i found it best to not test the macroexpansion in cljs
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.
i can help, i've done this many times
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
alternatively, is there a version of defn that's a function and not a macro?
ah hm, i guess now i need to parse the params myself ... is there some helper function for that?
oh no https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L225
i wonder if i want macroexpand-1 or something like that
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)))is there a way in Conjure to see the full stack trace for an error?
There is <prefix>ve for "view error"
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.
@olical do you mean this inline highlight? that gets cut off and doesn't show me the stack trace at all.
right i see, it's put the full thing in the log where i can see it all
that still has the original problem though, it's printed as a host traceback instead pretty printing the SCI traceback
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.
Oh wait, ve and then check the log, not online?
if it helps, i have an SCI pretty-printer for flower that you could borrow from
Inline*
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&cid=C03S1KBA2
Are you calling print-stack-trace or is this an internal failure within some error handling code I wonder?
internal failure i believe
i'm just calling *e
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
Because if so, definitely a bug and we can get that fixed.
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 ...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?
Assuming you're talking about the most recent uncaught exception, does just typing *e not give you what you need?
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]]}That looks like a new issue. Your resolve failed it seems? Trigger the original exception and try *e again.
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.
If it's valid edn could you pipe it to the Clojure pretty printer?
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
i don't want to see :line 1 :column 1, it's just noise
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.
And yeah I was referring to pprint
i think i'm going to use JVM clojure for my repl, bb nrepl-server doesn't support deps.edn either
and then it's a moot point
Might be worth asking in #babashka since this seems specific to that flavor.
this seems like something that would be nice to ship with Conjure by default, since Conjure defaults to bb nrepl-server