Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining a function in a macro: can't use qualified name as parameter

Tags:

clojure

I want a macro to define functions that return the form they were called as, e.g. (func 1 (a b)) returns (func 1 (a b)). I also want to allow input verification for these functions to make sure I'm not introducing any bugs. (These forms will later be evaluated, but that code hasn't been written yet.)

I keep getting this error, though.

(defmacro defecho
  "Echo function call after asserting a few things about the input"
  ([f] `(defecho ~f nil nil))
  ([f assertions] `(defecho ~f assertions nil))
  ([f assertions assert-failed-message]
   `(defn ~f [& body]                ; define a function
      ~(when-not (nil? assertions)   ; if given a function for input validation
         `(assert (~assertions body) ; define the function to assert this as true
                  ~assert-failed-message)) ; with a given error message
      (conj body (quote ~f)))))      ; return the (f ~@body) list

(defecho my-test
  #(< 2 (count %))
  "Must be greater than zero")
  1. Unhandled clojure.lang.Compiler$CompilerException
    Error compiling:
    /private/var/...228.clj:1:1
    Can't use qualified name as parameter: my-test/body
    
  2. Caused by java.lang.RuntimeException
    Can't use qualified name as parameter: my-test/body
    
like image 832
Sean Allred Avatar asked Sep 27 '22 09:09

Sean Allred


2 Answers

You can't use qualified symbols as function parameters. Observe that

`body

evaluates to current-namespace/body

In a syntax-quote, you can always unquote a non-syntactic quote to get an unqualified symbol:

`~'body

evaluates to body. (Notice that unquoting here serves evaluation of the inner quoting itself).

However, in this case you should generate a symbol instead, because if a user uses the symbol body within e. g the code of assert-failed-message you don't want his binding of body to be shadowed with yours (observe that his code is evaluated when the generated function is actually called).

It is common practice to generate symbols for that purpose, either using gensym or a symbol ending with a hash, which syntax quote will expand to a gensym call..

`body#

evaluates to the (unqualified!) symbol body__34343__auto__ where the number differs on each invocation and is guaranteed to be different each time.

Since you refer to body from within two different syntax quotes, I picked the gensym option in combination with let so that only one symbol is generated.

(defmacro defecho ; overloads stripped for brevity
  [f assertions assert-failed-message]
  (let [args-sym (gensym "body")] ; define a symbol for function arglist
    `(defn ~f [& ~args-sym]                ; define a function
       ~(when-not (nil? assertions)        ; if given a function for input validation
          `(assert (apply ~assertions ~args-sym) ; define the function to assert this as true
                   ~assert-failed-message)) ; with a given error message
       (conj ~args-sym (quote ~f)))))
like image 169
Leon Grapenthin Avatar answered Nov 15 '22 11:11

Leon Grapenthin


You can make your life a little simpler by using a proper function for the heavy lifting and use the macro only for the syntax sugar:

(defmacro defecho
  "Echo function call after asserting a few things about the input"
  ([f] `(defecho ~f nil nil))
  ([f assertions] `(defecho ~f assertions nil))
  ([f assertions assert-failed-message]
   `(def ~f (echo-function (quote ~f) ~assertions ~assert-failed-message))))

(defn echo-function [f assertion assert-failed-message]
  (fn [& body]
    (when assertion
      (assert (assertion body)
              assert-failed-message))
    (conj body f)))
like image 27
Sean Allred Avatar answered Nov 15 '22 09:11

Sean Allred