Search code examples
clojuremacrosfunctional-programminglisp

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))))))

Solution

  • 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.