Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does not Clojure support private functions in macro?

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))))))
like image 563
Ertuğrul Çetin Avatar asked Sep 23 '16 08:09

Ertuğrul Çetin


2 Answers

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.

like image 70
Piotrek Bzdyl Avatar answered Nov 20 '22 17:11

Piotrek Bzdyl


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#)
like image 27
joelittlejohn Avatar answered Nov 20 '22 16:11

joelittlejohn