I was trying to implement xor macro and came up with a problem.
I couldn't use private function in a macro.
Here is the example:
private function
(defn :^private xor-result
[x y]
(if (and x y)
false
(or x y)))
macro
(defmacro xor
([] true)
([x] x)
([x & next]
`(let [first# ~x
second# ~(first next)]
(if (= (count '~next) 1)
(xor-result first# second#)
(xor (xor-result first# second#) ~@(rest next))))))
Here is the Error:
CompilerException java.lang.IllegalStateException: var: #'kezban.core/xor-result is not public
Problem solves when I remove ^:private flag.
Question is: What is the reason of this behaviour?
UPDATE: I can use private function with the following approach.
private function
(defn ^:private xor-result
[x y]
(if (and x y)
false
(or x y)))
new macro
(defmacro xor
([] true)
([x] x)
([x & next]
(let [first x
second `(first '(~@next))
result (xor-result (eval first) (eval second))]
`(if (= (count '~next) 1)
~result
(xor ~result ~@(rest next))))))
If you have a macro in ns1
:
(ns ns1)
(defn- my-fun [x] (first x))
(defmacro my-macro [x] (my-fun ~x))
And use it in another namespace:
(ns ns2
(:require [ns1 :refer [my-macro]]))
(my-macro [1 2])
The compiler will call the macro during compilation phase and it will generate code in ns2
namespace and will become:
(ns ns2
(:require [ns1 :refer [my-macro]]))
(ns1/my-fun [1 2])
and this code will be eventually compiled to byte code.
As you can see the compiler will see usage of a ns1
's private function in ns2
namespace and will complain about it.
To debug your macros you can use macroexpand
to see the result of applying your macro.
You also need to remember that your macros work on your program data: datastructures representing your code (symbols, lists, vectors etc.). For example in your second version of the macro it works symbols as they are, not runtime values bound to them:
(macroexpand '(xor true false))
;; => (if (clojure.core/= (clojure.core/count (quote (false))) 1) true (boot.user/xor true))
(macroexpand '(xor (zero? 1) (zero? 0)))
;; => (if (clojure.core/= (clojure.core/count (quote ((zero? 0)))) 1) false (boot.user/xor false))
As you can see your xor-result
function won't be called with the actual runtime values but rather with the data representing your code. xor-result
is called in your macro directly during compile time. In the first version of your macro it is used inside of the code generated by the macro and is not called during compilation.
There's a hack you can use if you really do want to access private vars from within a public macro that will be used by other namespaces.
When you resolve the value of a var by referring to it in your code, Clojure checks whether the var is public or private and the compiler will complain if you attempt to access a private var. However you can refer explicitly to the var itself (rather than its value) using the #'
syntax, and Clojure will allow this kind of reference even to a private var. You should use a fully-qualified name (use the full namespace name) so that you don't require any particular namespace alias to exist.
So, assuming that the function xor-result
lives in a namespace called mynamespace.core
, you would invoke the function like:
(#'mynamespace.core/xor-result first# second#)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With