I have the following Clojure wrapping macro with 1 parameter:
(defmacro with-init-check
"Wraps the given statements with an init check."
[body]
`(if (initialized?)
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
I want to add ip-version to it so I can check if just :IPv6 or :IPv4 is initialized. However the parameter does not get passed through when I try this:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version body]
`(if (initialized? ip-version)
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
When I use it like this, I get "no such var" at the "if-let [location..." line:
(defn- lookup-location
"Looks up IP location information."
[ip ip-version]
(with-init-check ip-version
(if-let [location (get-location ip ip-version)]
{:ip ip
:countryCode (.countryCode location)
:countryName (.countryName location)
:region (.region location)
:city (.city location)
:postalCode (.postalCode location)
:latitude (.latitude location)
:longitude (.longitude location)
:dma-code (.dma_code location)
:area-code (.area_code location)
:metro-code (.metro_code location)})))
How can I get the ip-version to the initialized? function?
Unquote it with ~:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version body]
`(if (initialized? ~ip-version) ; unquoted with ~
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
Deducing from your docstring, you probably also want to allow multi-expression bodies and unquote-splice them in a do expression:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version & body] ; multi-expression bodies with &
`(if (initialized? ~ip-version)
(do ~#body) ; unquote-spliced with ~#
(throw (IllegalStateException. "GeoIP db not initialized."))))
Related
Emacs version: 26.3
Slime version: 2.26.1
I start up Emacs.
I open up a simple .lisp file.
(defun testfn (x y)
(+ x y))
(defmacro testmc (form)
form
`(list 1 2 3))
I place my cursor over the symbol defun and issue the keyboard-command M-. (slime-edit-definition).
This should bring me to the definition of defun.
But it doesn't.
It brings me here:
I place my cursor over the symbol defmacro and issue the keyboard-command M-. (slime-edit-definition).
This should bring me to the definition of defmacro.
But it doesn't.
It brings me here:
Why does it do this & how do I fix this
Notice there is a warning in the REPL when trying to find the source of DEFUN:
WARNING: inconsistent 2 form-number-translations
You can replicate it yourself in the REPL:
CL-USER> (let ((slynk::*buffer-package* (find-package :cl))
(slynk::*buffer-readtable* *readtable*))
(slynk:find-definitions-for-emacs "DEFUN"))
WARNING: inconsistent 2 form-number-translations
(("(DEFMACRO DEFUN)"
(:LOCATION (:FILE "/home/chris/data/src/sbcl/src/code/macros.lisp")
(:POSITION 4140)
(:SNIPPET "(setq doc nil)
(let* (;; stuff shared between LAMBDA and INLINE-LAMBDA and NAMED-LAMBDA
(lambda-guts `(,#decls (block ,(fun-name-block-name name) ,#forms)))
(lambda `(lambda ,lambda-list ,#lambda-guts))
(named-lambda `("))))
To find where the warning comes from, you could do as I did first and do a textual search on the repository, or you could use the following alternate method that works better, namely invoke the debugger on warnings:
(handler-bind ((warning (lambda (c) (invoke-debugger c))))
(let ((slynk::*buffer-package* (find-package :cl))
(slynk::*buffer-readtable* *readtable*))
(slynk:find-definitions-for-emacs "DEFUN")))
This comes from SLYNK-SBCL::FORM-NUMBER-POSITION, and the interesting value in the debugger is the source location obtained from SBCL:
#<SB-INTROSPECT:DEFINITION-SOURCE {10369C50F3}>
--------------------
The object is a STRUCTURE-OBJECT of type SB-INTROSPECT:DEFINITION-SOURCE.
PATHNAME: #P"SYS:SRC;CODE;MACROS.LISP"
FORM-PATH: (5)
FORM-NUMBER: 89
CHARACTER-OFFSET: 3917
FILE-WRITE-DATE: 3825178034
PLIST: NIL
DESCRIPTION: NIL
It says the source is the fifth toplevel form in the file (which corresponds to the character offset), and from here, the FORM-NUMBER is the 89th form in a depth-first search walk of the form (this comes from the structure's docstring).
But, if I recompile the function FORM-NUMBER-POSITION with DEBUG set to 3, the toplevel form read at this position, TLF is NIL:
1: (SLYNK-SBCL::FORM-NUMBER-POSITION #S(SB-INTROSPECT:DEFINITION-SOURCE :PATHNAME #P"SYS:SRC;CODE;MACROS.LISP" :FORM-PATH (5) :FORM-NUMBER 89 :CHARACTER-OFFSET 3917 :FILE-WRITE-DATE 3825178034 :PLIST NIL..
Locals:
DEFINITION-SOURCE = #S(SB-INTROSPECT:DEFINITION-SOURCE :PATHNAME #P"SYS:SRC;CODE;MACROS.LISP" :FORM-PATH (5) :FORM-NUMBER 89 :CHARACTER-OFFSET 3917 :FILE-WRITE-DATE 3825178034 :PLIST NIL :DESCRIPTION NIL)
FORM-NUMBER = 89
PATH-TABLE = #((0 0))
POS-MAP = #<HASH-TABLE :TEST EQ :COUNT 126 {103B227EA3}>
POS-MAP#1 = #<HASH-TABLE :TEST EQ :COUNT 126 {103B227EA3}>
STREAM = #<SB-IMPL::STRING-INPUT-STREAM {7F3E0350D953}>
TLF = NIL
TLF#1 = NIL
TLF-NUMBER = 5
In read-source-form, you can see that the form is being read inside a (ignore-errors (read ...)) form, which returns NIL in case of error. I tried calling (read ...) only but this somehow did not invoke the debugger, so I did the same thing as above and explicitly invoked it on any condition.
There is an error, namely that the package "SB-XC" does not exist, which is expected since, if I am not mistaken, this is a package that only exists during the compilation of SBCL itself.
I think you should contact the SBCL SLY developers and file a bug for this directly, they would certainly have a better idea of how to fix the behaviour (feel free to link to your question in addition to giving the usual details of the bug report).
I'm seeing pretty much what you're seeing.
Defun (line 280 of defboot.lisp) is a macro, which is defined in terms of defun-expander (line 230 of defboot.lisp) which is what you're seeing.
Whereas, defmacro takes you directly to its definition (line 15 of defmacro.lisp) which is what you're seeing.
It seems to be doing useful things.
I defined a new function 'addmore'
(defun addmore (x y z)
(testfn x (testfn y z)))
I compiled it all, and M-. on 'addmore' takes me to the definition of testfn.
So I think it's all working.
Well, the title is a mouthful, so I will expand on it. I have the following code (it is incomplete, mostly just for illustration):
(use '[clojure.zip :only [up down right node])
(defn in-zip? [form]
(contains? (-> 'clojure.zip ns-publics vals set) (first form)))
(defn do-something-to-zip-form [fx loc rest]
;; this is where I would do the transform, but for now, I will just
;; return the actual form
form)
(defn transform-zip [form]
(if (in-zip? form)
(do-something-to-zip-form form)
form))
(defmacro gozip [body]
(clojure.walk/postwalk transform-zip body))
The purpose of in-zip? is to take a form and determine whether the evaluated form calls to a function in clojure.zip . So, something like (is-zip? '(clojure.zip/down loc) or (is-zip? '(up loc)) should return true, any form that isn't calling a function within clojure.zip should return false.
I want to be able to call gozip with a form, and have every call to a function in clojure.zip be replaced by my do-something-to-zip-form transformation. Some examples:
(gozip (-> loc down right right (clojure.zip/update 3))
In the above expression, I would like it to run the transform in 5 places (loc,down,right,right, and update) because those are all functions within clojure.zip.
(gozip (let [d (down loc)] (node loc)))
In the above expression, I would like to run transform in 2 places (down, node).
Sorry about being so pedantic about explaining what I am interested in, it is just that I am having trouble explaining exactly what I want, seems easier through examples. I am looking to use gozip in clojure and clojurescript code.
I have created a macro which creates a named dispatcher with 3 associates functions get-dispatcher, set-dispatcher and call-dispatcher to work with the dispatcher (they get a dispatching function, add one or call one). It all works just fine! However, now I want to automate the related functions names creation, thus I am putting all these internals of the macro into a let which defines that simple construction function. Note that in the code below only the get- function's name is constructed with that automation. The set- and call- ones name creation still has that manual smell.
(defmacro create-dispatcher [name]
;creates a set of dispatching functions tagged
`(do
;define dispatcher
(def ~(symbol name) ~(atom {}))
(let
[name-w-prefix (fn [x] (~(symbol (str x "-" name))))]
; -- define getter
(defn (name-w-prefix "get")
"get-dispatcher [tag]: get a dispatcher fn by tag"
(~'[] (println "no tag is provided for '" ~(str name) "' dispatcher"))
(~'[tag]
(do
(println "dispatcher '" ~(str name) "' called with '" ~'tag "' tag")
; return the tagged dispatcher
( (keyword ~'tag) #~(symbol name) )))
)
; -- define caller
(defn ~(symbol (str "call-" name))
"get-dispatcher [tag & args]: call a dispatcher fn by tag and apply to the args"
~'[tag & args]
(apply (~(symbol (str "get-" name)) ~'tag) ~'args)
)
; -- define setter
(defn ~(symbol (str "set-" name))
~'[tag fn]
"add-dispatcher [tag fn]: add a dispatcher fn associated with the tag"
(swap! ~(symbol name) assoc (keyword ~'tag) ~'fn)
)
)
; -- report
(println "created dispatcher set for '" ~(str name) "' ok!")
))
However, there is a problem. The name-w-prefix in the let statement binding causes errors. How can I fix that?
(also any advices on improvement are welcome since I am a newb and that is almost the first thing that I wrote in Clojure)
All symbols in a macro are resolved in the current namespace and expected to evaluate to a var. You could quote the name-w-prefix symbol, but that would risk colliding with symbols passed in to the macro during macro expansion. So, Clojure provides a special syntax for use in syntax-quoted forms for generating symbols - just append a # to the end of the symbol and Clojure will treat that as a quoted, auto-generated symbol. So in this case, replace occurrences of name-w-prefix with name-w-prefix# and you should be good to go.
Taking a step back and looking at what your overall goal is, I think you should move the name-w-prefix definition outside the syntax quotes and then use syntax-escape to call it. Otherwise, you'll get still more errors because defn requires a symbol, so once expanded the macro must produce a defn form that has a symbol as its second item. Something along the lines of:
(defmacro create-dispatcher [name]
(let [name-w-prefix #(symbol (str % "-" name))]
`(do
(def ~(symbol name) (atom {}))
(defn ~(name-w-prefix "get")
([] (println "no tag provided"))
([tag#] (println "called with tag" tag#))))))
Note that I've changed ~'[tag] to [tag#] in the defn body in accordance with what I was talking about above.
I'm in the process of learning Clojure macros, and I'm getting a NullPointerException when trying to use macroexpand-1 on this macro:
(def config {:ns 'bulbs.neo4jserver.client,
:root-uri "http://localhost:7474/db/data/"})
(def data {:name "James"})
(defmacro create
[config data]
`(~(ns-resolve (:ns config) 'create-vertex) config data))
(macroexpand-1 '(create config data))
Trying to compile this returns:
Unknown location:
error: java.lang.NullPointerException
Compilation failed.
But evaluating the macro's body...
`(~(ns-resolve (:ns config) 'create-vertex) config data)
...returns this...
(#'bulbs.neo4jserver.client/create-vertex bulbs.vertices/config bulbs.vertices/data)
...which is what I think I want.
UPDATE: If I manually replace (:ns config) with 'bulbs.neo4jserver.client then the error goes away -- how do you make (:ns config) play nice?
You're trying to mix macroexpand-time and runtime information. The local "config" does not contain the contents of the #'config var, but instead is the symbol 'config.
If you look at the full stack trace, not just the error message, you'll see that ns-resolve is being passed a nil:
user=> (pst)
NullPointerException
java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:796)
clojure.lang.Namespace.find (Namespace.java:188)
clojure.core/find-ns (core.clj:3657)
clojure.core/the-ns (core.clj:3689)
clojure.core/ns-resolve (core.clj:3879)
clojure.core/ns-resolve (core.clj:3876)
clj.core/create (NO_SOURCE_FILE:7)
Once you understand the following you will understand your original problem:
user=> (def bar [1 2 3])
user=> (defmacro foo [x] [(class x) (pr-str x)])
user=> (foo (get bar 2))
[clojure.lang.PersistentList "(get bar 2)"]
Why is this a macro in the first place? It seems a normal function would do in this case.
Remember that config is bound to the literal value you entered, so if you do
(def c {:ns 'foo})
(create c 1)
config is going to be just 'c, not the map referenced by c at runtime.
I am currently developing a small CMS using the wonderful Enlive as templating engine. Enlive has a macro called at that takes a node (a map) specifying the HTML snippet and an arbitrary number of tuples each consisting of a selector (a vector) and a transformation (a closure).
(at a-node
[:a :selector] a-transformation
[:another :selector] another-transformation
...)
Now I want to generate the tuples depending upon incoming data/context. I have tried a lot of different things without success. For example
(let [this (repository/u "http://example.com/ACMECorp")
statements (repository/find-by-subject this)
context {:depth 1}]
`(at (snippet-for 'this 'context)
[root] (set-attr :about (str 'this))
~#(loop [rules []
st statements]
(if-not (seq st)
rules
(recur (conj rules
`[:> (attr= :property ~(str (repository/predicate (first st))))]
`(content (renderit ~(repository/object (first st)) 'context)))
(rest st))))))
Any help is highly appreciated.
-Jochen
Clojure is a Lisp, so you can always fallback to building the code you'd want as a list, and call eval on it. I'm not 100% sure about the code you gave, but I'd guess you just want to enclose your whole syntax-quote in an eval call.
(let [this (repository/u "http://example.com/ACMECorp")
statements (repository/find-by-subject this)
context {:depth 1}]
(eval `(at (snippet-for 'this 'context)
[root] (set-attr :about (str 'this))
~#(loop [rules []
st statements]
(if-not (seq st)
rules
(recur (conj rules
`[:> (attr= :property ~(str (repository/predicate (first st))))]
`(content (renderit ~(repository/object (first st)) 'context)))
(rest st)))))))
Not sure if they are interchangeable, but take a look at the at* function. Seems to me that your problem is at being a macro.
EDIT: They're not. Call it like this:
(at* a-node
[[:a :selector] a-transformation
[:another :selector] another-transformation
...])