What's causing this error?
(defmacro defn+
[name & stmts]
`(defn htrhrthtrh ~#stmts))
(defn+ init
[]
(js/alert "hi"))
--
java.lang.AssertionError: Assert failed: Can't def ns-qualified name
(not (namespace sym))
htrhrthtrh gets namespace-qualified by syntax-quote in the output, so the result looks like
(defn some.namespace/htrhrthtrh [] (js/alert "hi"))
which is incorrect, as explained in the exception message.
Presumably you'll want to use ~name in place of htrhrthtrh to include the name argument to defn+ in the output; this, or anything along similar lines, would fix the problem. To hard-wire this exact name you'd have to say ~'htrhrthtrh.
Related
In Python I'm able to do something like:
fast_thing_available = True
try:
import fast_thing
except ImportError:
fast_thing_available = False
# snip
if fast_thing_available:
default = fast_thing
else:
default = slow_thing
Is it possible to do the same thing in Clojure?
I've tried next, but it fails (e.g. import is still required):
(ns sample.ns)
(def ^:private long-adder-available (atom false))
(try
(do
(import 'java.util.concurrent.atomic.LongAdder)
(swap! long-adder-available (constantly true)))
(catch ClassNotFoundException e ()))
(when (true? #long-adder-available)
(do
; Here I'm using LongAdder itself
))
Code throws IllegalArgumentException: unable to resolve classname: LongAdder even if LongAdder itself is not available.
As pointed out by #amalloy in the comments, the code inside when does not compile. I am not sure if there is a way to re-write that code so it compiles. However it is possible to avoid compiling it altogether. Clojure macros can be used to exclude code from compilation.
A macro can attempt importing a class and only if that succeeds emit the the code using the class. There are simpler ways to check if a class exists in the classpath, but it is important to call import at compile time. That way the code can use simple class names (like LongAdder).
When applied to this problem, the solution might look like the sample below. The piece of code calling import is a bit ugly with eval etc. but it is tough to pass non-literal arguments to import. If there is no need for this code to be generic, the class name can be hard-coded and a few other things can be simplified.
(ns sample.core)
(defmacro defn-if-class
"If clazz is successfully imported, emits (defn name args then)
Emits (defn name args else) otherwise."
[clazz name args then else]
(if (try (eval `(import ~clazz)) true (catch Exception e nil))
`(defn ~name ~args ~then)
`(defn ~name ~args ~else)))
(defn-if-class java.util.concurrent.atomic.LongAdder
foo []
;; if class exists
(doto (LongAdder.)
(. increment)
(. sum))
;; else
"fallback logic in case the class is not in classpath")
(defn -main [& args]
(println (foo)))
I should mention that the answer is heavily inspired by Jay Fields' blog post "Clojure: Conditionally Importing" and this answer.
I'm using slingshot's throw+ macro to raise an exception that looks like:
(throw+ {:type ::urlparse})
The type checker doesn't like it:
Type Error (stream2es/http.clj:79:17) Bad arguments to apply:
Target: [String t/Any * -> String]
Arguments: (PersistentList String)
in: (clojure.core/apply clojure.core/format (clojure.core/list "throw+: %s" (clojure.core/pr-str %)))
Type Checker: Found 1 error
The macro in slingshot looks like:
(defmacro throw+
([object]
`(throw+ ~object "throw+: %s" (pr-str ~'%)))
([object message]
`(throw+ ~object "%s" ~message))
([object fmt arg & args]
`(let [environment# (s/environment)
~'% ~object
message# (apply format (list ~fmt ~arg ~#args))
stack-trace# (s/stack-trace)]
(s/throw-context ~'% message# stack-trace# environment#)))
([]
`(s/rethrow)))
I've tried various ann ^:no-check forms on apply and format and none works. Since it's a macro, I'm assuming I can't annotate it since it replaces the code that's there. But I also can't rewrite the code in the macro like was suggested in this other answer, because it's in a library. How do I gradually type in this case?
If you’re not able to rewrite the implementation of throw+, I suggest a wrapper macro like this.
(defmacro typed-throw+ [object]
`(let [o# ~object]
(t/tc-ignore
(throw+ o#))
(throw (Exception.)))) ; unreachable
;; other arities are an exercise ..
This way, you still type check the argument, and core.typed still thinks throw+ always throws an exception — it doesn't really know that, but the final throw clause allows core.typed to give the entire expression type Nothing.
The real answer should be we can improve apply to know that applying a non-empty list will satisfy at least one argument, however this answer should work today.
I want to display random (doc) page for some namespace.
The random function name I can get by:
user=> (rand-nth (keys (ns-publics 'clojure.core)))
unchecked-char
When I try to pass this to (doc) I get this:
user=> (doc (rand-nth (keys (ns-publics 'clojure.core))))
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.Symbol clojure.core/ns-resolve (core.clj:3883)
I'm new to Clojure and I'm not sure how to deal with this... I tried to convert this into regexp and use (find-doc) but maybe there is a better way to do this...
Explanation
The problem here is that doc is a macro, not a function. You can verify this with the source macro in the repl.
(source doc)
; (defmacro doc
; "Prints documentation for a var or special form given its name"
; {:added "1.0"}
; [name]
; (if-let [special-name ('{& fn catch try finally try} name)]
; (#'print-doc (#'special-doc special-name))
; (cond
; (special-doc-map name) `(#'print-doc (#'special-doc '~name))
; (resolve name) `(#'print-doc (meta (var ~name)))
; (find-ns name) `(#'print-doc (namespace-doc (find-ns '~name))))))
If you're new to Clojure (and lisps), you might not have encountered macros yet. As a devastatingly brief explanation, where functions operate on evaluated code, macros operate on unevaluated code - that is, source code itself.
This means that when you type
(doc (rand-nth (keys (ns-publics 'clojure.core))))
doc attempts to operate on the actual line of code - (rand-nth (keys (ns-publics 'clojure.core))) - rather than the evaluated result (the symbol this returns). Code being nothing more than a list in Clojure, this is why the error is telling you that a list can't be cast to a symbol.
Solution
So, what you really want to do is evaluate the code, then call doc on the result. We can do this by writing another macro which first evaluates the code you give it, then passes that to doc.
(defmacro eval-doc
[form]
(let [resulting-symbol (eval form)]
`(doc ~resulting-symbol)))
You can pass eval-doc arbitrary forms and it will evaluate them before passing them to doc. Now we're good to go.
(eval-doc (rand-nth (keys (ns-publics 'clojure.core))))
Edit:
While the above works well enough in the repl, if you're using ahead ahead-of-time compilation, you'll find that it produces the same result every time. This is because the resulting-symbol in the let statement is produced during the compilation phase. Compiling once ahead of time means that this value is baked into the .jar. What we really want to do is push the evaluation of doc to runtime. So, let's rewrite eval-doc as a function.
(defn eval-doc
[sym]
(eval `(doc ~sym)))
Simple as that.
The try is in one macro, the catch in a second one that is called by the first. How to get the following to work?
(defmacro catch-me []
`(catch ~'Exception ~'ex
true))
(defmacro try-me []
`(try (+ 4 3)
(catch-me)))
Expanding try-me looks good:
(clojure.walk/macroexpand-all '(try-me))
yields
(try (clojure.core/+ 4 3) (catch Exception ex true))
but calling (try-me) yields:
"Unable to resolve symbol: catch in this context",
which, BTW, is also the message you would get in the REPL when using catch when not in a try.
UPDATE:
This is how I can get it to work (thanks, #Barmar), here you can see the actual context of my code:
(defmacro try-me [& body]
`(try
~#body
~#(for [[e msg] [[com.mongodb.MongoException$Network "Database unreachable."]
[com.mongodb.MongoException "Database problem."]
[Exception "Unknown error."]]]
`(catch ~e ~'ex
(common/site-layout
[:div {:id "errormessage"}
[:p ~msg]
[:p "Error is: " ~e]
[:p "Message is " ~'ex]])))))
but this is what I was hoping for (using a separate macro catch-me):
(defmacro try-me [& body]
`(try
~#body
(catch-me com.mongodb.MongoException$Network "Database unreachable.")
(catch-me com.mongodb.MongoException "Database problem.")
(catch-me Exception "Unknown error.")))
I think this would be easier to write / maintain.
Any ideas? I need syntax-quoting because I am passing parameters, that is why unfortunately Arthur's answer cannot be applied (or can it somehow?), but I didn't post my actual context until just now.
The reason you get that error is because the syntax for try is:
(try expr* catch-clause* finally-clause?)
This means that there can be any number of expr forms before the catch and finally clauses. try scans the exprs until it finds one that begins with catch or finally. It does this before expanding any macros, since it's just trying to figure out where the exprs and and the catch/finally clauses begin. It collects all the catch and finally clauses and establishes the appropriate error handling environment for them.
Once it does this, it executes all the expr forms normally. So it expands their macros, and then executes them. But catch is not a function or special form, it's just something that try looks for in the earlier step. So when it's executed normally, you get the same error as when you type it into the REPL.
What you should probably do is write a macro that you wrape around your entire code that expands into the try/catch expression that you want. Without an example of what you're trying to accomplish, it's hard to give a specific answer.
The short answer is YES, though nesting macros with special forms can lead to some double-quoting headaches like this one. It is necessarily to prevent the symbols from being evaluated at both levels of expansion:
user> (defmacro catch-me []
'(list 'catch 'Exception 'ex
'true))
user> (defmacro try-me []
`(try (+ 4 3)
~(catch-me)))
#'user/try-me
user> (try-me)
7
and to see that it catches the exception as well:
user> (defmacro try-me []
`(try (/ 4 0)
~(catch-me)))
#'user/try-me
user> (try-me)
true
I am trying to better understand listing 13.3 in The Joy of Clojure. It is a macro that generates other macros (much like how primitive array functions are implemented in Clojure 1.4).
I want to write a macro that, when run, simply prints the suffix of the generated macro. i.e.
user=> (nested-macro joe)
user=> (nested-macro-named-joe)
hello from joe
nil
I am having trouble making this work.
Here is what I've tried:
Attempt 1
(defmacro nested-macro [name]
`(defmacro ~(symbol (str "nested-macro-named-" name))
[]
`(println "hello from " ~name)))
Output:
hello from #<core$name clojure.core$name#502c06b2>
Attempt 2
(defmacro nested-macro [name]
(let [local-name name]
`(defmacro ~(symbol (str "my-custom-macro-named-" ~local-name))
[]
`(println "hello from " ~local-name))))
Error
IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote clojure.lang.Var$Unbound.throwArity (Var.java:43)
Attempt 3:
(defmacro nested-macro [name]
(let [local-name name]
`(defmacro ~(symbol (str "nested-macro-named-" name))
[]
`(println "hello from " ~(symbol local-name)))))
Compiler Error:
CompilerException java.lang.RuntimeException: No such var: joy.dsl/local-name
Just for the heck of it, I've also tried adding # to the local variables, with similar results as above but with "auto" names, such as local-name__1127__auto__ I don't see that being as part of the solution, however.
How can I make this work?
To know what is going wrong with macros I always use macroexpand-1.
From your first example:
(macroexpand-1 '(nested-macro joe))
Results in:
(clojure.core/defmacro nested-macro-named-joe []
(clojure.core/seq
(clojure.core/concat
(clojure.core/list (quote clojure.core/println))
(clojure.core/list "hello from ")
(clojure.core/list clojure.core/name))))
If you look at the last param, it shows that you are using clojure.core/name, which probably is not what you want, as you actually want the parameter named "name".
To fix it, just add another unquote to the name param, but as the name param is actually a symbol, what you really want is to get the name of it as in:
(defmacro nested-macro [the-name]
`(defmacro ~(symbol (str "nested-macro-named-" the-name))
[]
`(println "hello from " ~~(name the-name))))