为什么Clojure不支持宏中的私有功能?

Why does not Clojure support private functions in macro?

我正在尝试实现xor宏,并提出了一个问题。

我无法在宏中使用私有函数。

这里是示例:

私有功能

1
2
3
4
5
(defn :^private xor-result
  [x y]
  (if (and x y)
    false
    (or x y)))

1
2
3
4
5
6
7
8
9
(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))))))

这是错误:

1
CompilerException java.lang.IllegalStateException: var: #'kezban.core/xor-result is not public

当我删除^:private标志时,问题解决了。

问题是:这种行为的原因是什么?

更新:我可以通过以下方法使用私有函数。

私有功能

1
2
3
4
5
(defn ^:private xor-result
  [x y]
  (if (and x y)
    false
    (or x y)))

新宏

1
2
3
4
5
6
7
8
9
10
(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))))))

如果您在ns1中有一个宏:

1
2
3
4
5
(ns ns1)

(defn- my-fun [x] (first x))

(defmacro my-macro [x] (my-fun ~x))

并在另一个命名空间中使用它:

1
2
3
4
(ns ns2
  (:require [ns1 :refer [my-macro]]))

(my-macro [1 2])

编译器将在编译阶段调用宏,并将在ns2命名空间中生成代码,并将变为:

1
2
3
4
(ns ns2
  (:require [ns1 :refer [my-macro]]))

(ns1/my-fun [1 2])

,此代码最终将编译为字节码。

如您所见,编译器将在ns2名称空间中看到ns1的私有函数的用法,并且会抱怨它。

要调试宏,可以使用macroexpand查看应用宏的结果。

您还需要记住,您的宏适用于程序数据:代表代码的数据结构(符号,列表,向量等)。例如,在您的宏的第二个版本中,它按原样运行符号,而不是绑定到它们的运行时值:

1
2
3
4
5
(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))

如您所见,您的xor-result函数将不会被实际的运行时值调用,而会被代表代码的数据调用。在编译期间直接在宏中调用xor-result。在您的宏的第一个版本中,它在宏生成的代码内部使用,并且在编译期间不会调用。


如果您确实想从将由其他命名空间使用的公共宏中访问私有变量,则可以使用一种技巧。

当通过在代码中引用var的值来解析它的值时,Clojure会检查var是公共的还是私有的,并且如果您尝试访问私有的var,则编译器会抱怨。但是,您可以使用#'语法显式地引用var本身(而不是其值),并且Clojure甚至允许对私有var进行这种引用。您应该使用标准名称(使用完整的名称空间名称),这样就不需要存在任何特定的名称空间别名。

因此,假设函数xor-result驻留在名为mynamespace.core的命名空间中,则将调用以下函数:

1
(#'mynamespace.core/xor-result first# second#)