我的谜题是以下示例:
(defmacro macro1 [x]
(println x))
(defn func1 [x]
(println x))
(defmacro macro2 [x]
`(macro1 ~x)
(func1 x))
(defmacro macro3 [x]
(func1 x)
`(macro1 ~x))
(println "macro2")
(macro2 hello)
(println "macro3")
(macro3 hello)
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,输出是:
macro2
hello
macro3
hello
hello
Run Code Online (Sandbox Code Playgroud)
为什么macro2和macro3的输出不同?根据我的理解,宏内部宏的所有调用都可以用函数代替(除了重用的原因).我的理解有什么不对吗?
谢谢迈克尔的澄清.我的一般问题是如何在宏内部使用函数或宏来操作s-expression.我想知道它们是否可以交换使用,除了它们在不同的阶段被唤醒.另一个例子:
(defn manipulate-func [x]
(list + x 1))
(defmacro manipulate-macro [x]
(list + x 1))
(defmacro macro1 [x y]
[(manipulate-func x) `(manipulate-macro ~y)])
(println (clojure.walk/macroexpand-all '(macro1 (+ 1 2) (+ 3 4))))
;; [(#<core$_PLUS_ clojure.core$_PLUS_@332b9f79> (+ 1 2) 1) (#<core$_PLUS_ clojure.core$_PLUS_@332b9f79> (+ 3 4) 1)]
Run Code Online (Sandbox Code Playgroud)
macro2不打电话macro1.看看它的身体:
`(macro1 ~x)
(func1 x)
Run Code Online (Sandbox Code Playgroud)
第一行是语法引用的; 它的值是表单的列表结构(user/macro1 x-value)(假设macro1在user命名空间中定义; x-value这里是提供的文字参数macro2),它没有副作用.由于没有副作用且值被丢弃,因此该行无效.
回应编辑:
首先,区分调用宏体内的另一个宏与发出对另一个宏的调用是很重要的:
(defmacro some-macro []
...)
;; calls some-macro:
(defmacro example-1 []
(some-macro))
;; emits a call to some-macro:
(defmacro example-2 []
`(some-macro))
Run Code Online (Sandbox Code Playgroud)
其次,在宏的体内调用函数和宏的情况下,必须记住运行时和编译时的相关概念是什么:
宏调用的函数将在宏扩展器的运行时调用,从用户代码的角度来看是编译时间;
编译宏体时,宏将调用宏.
如果宏发出对另一个宏的调用,则与发出的宏调用相关的运行时和编译时的概念将与原始宏调用相关的概念相同.如果宏调用另一个宏,它们会向后移动一步,就像它一样.
为了说明,让我们考虑一个将其所有工作委托给辅助函数的宏:
(defn emit-abc [abc-name [a b c]]
`(def ~abc-name {:a ~a :b ~b :c ~c}))
(defmacro defabc [abc-name abc-vals]
(emit-abc abc-name abc-vals))
Run Code Online (Sandbox Code Playgroud)
来自REPL:
user> (defabc foo [1 2 3])
#'user/foo
user> foo
{:a 1, :c 3, :b 2}
Run Code Online (Sandbox Code Playgroud)
如果emit-abc它本身就是一个宏,那么上面的定义defabc甚至都不会编译,因为它emit-abc会尝试对文字符号进行解构abc-vals,抛出一个UnsupportedOperationException.
这是另一个例子,可以更容易地解释发生了什么:
(let [[a b c] [1 2 3]]
(defabc foo [a b c]))
Run Code Online (Sandbox Code Playgroud)
defabc接收三个文字符号的向量a,b并c作为第二个参数; 它无法访问运行时值1,2并且3.它将这个精确的符号向量传递给函数emit-abc,然后函数可以到达此向量并提取符号以生成映射{:a a :b b :c c}.此地图成为defabc通话的扩展.在运行时a,b和c转出被绑定到值1,2和three,所以地图{:a 1 :b 2 :c 3}被产生.
假设我们试着写emit-abc与同一机构(只是改变宏defn以defmacro在其定义).然后我们无法从中调用它defabc,因为我们无法向它传达参数的实际值defabc.我们可以写
(emit-abc abc-name [(abc-vals 0) (abc-vals 1) (abc-vals 2)])
Run Code Online (Sandbox Code Playgroud)
进行defabc编译,但这最终将abc-name作为正在定义的Var的名称发出,并[a b c]在生成的代码中包含三次向量文字的代码.然而,我们可以发出呼叫:
`(emit-abc ~abc-name ~abc-vals)
Run Code Online (Sandbox Code Playgroud)
这按预期工作.
我认为您对宏和函数之间的区别感到困惑。
宏的结果应该几乎总是一个s 表达式,表示应用宏的源代码结果形式。这就是宏通常使用语法引用功能的原因,因为它可以通过~和~@转义轻松生成带有插入参数化值的源代码。
定义几个函数可能会帮助您了解这是如何工作的。让我们运行以下代码:
(defn testing-macro-2 [my-arg]
(macro2 my-arg))
(testing-macro-2 "macro 2 test")
(defn testing-macro-3 [my-arg]
(macro3 my-arg))
(testing-macro-3 "macro 3 test")
Run Code Online (Sandbox Code Playgroud)
这是我在 REPL 中得到的:
user=>
(defn testing-macro-2 [my-arg]
(macro2 my-arg))
my-arg
#'user/testing-macro-2
user=> (testing-macro-2 "macro 2 test")
nil
user=>
(defn testing-macro-3 [my-arg]
(macro3 my-arg))
my-arg
my-arg
#'user/testing-macro-3
user=> (testing-macro-3 "macro 3 test")
nil
Run Code Online (Sandbox Code Playgroud)
如您所见,my-arg在定义调用宏的函数时打印,而不是在我调用函数时打印。这是因为在 Clojure 编译器为该函数生成代码时会评估宏,所以这就是调用 的时间println。
但是,如果您使用语法引用 inmacro1使其返回代码而不是调用println,它返回nil,那么一切都会改变:
user=>
(defmacro macro1 [x]
`(println ~x))
#'user/macro1
user=>
(defn func1 [x]
(println x))
#'user/func1
user=>
(defmacro macro2 [x]
`(macro1 ~x)
(func1 x))
#'user/macro2
user=>
(defmacro macro3 [x]
(func1 x)
`(macro1 ~x))
#'user/macro3
user=>
(defn testing-macro-2 [my-arg]
(macro2 my-arg))
my-arg
#'user/testing-macro-2
user=> (testing-macro-2 "macro 2 test")
nil
(defn testing-macro-3 [my-arg]
(macro3 my-arg))
my-arg
#'user/testing-macro-3
user=> (testing-macro-3 "macro 3 test")
macro 3 test
nil
user=> (macro2 hello)
hello
nil
user=> (macro3 hello)
hello
CompilerException java.lang.RuntimeException: Unable to resolve symbol: hello in this context, compiling:(NO_SOURCE_PATH:107)
Run Code Online (Sandbox Code Playgroud)
由于在println评估宏时被调用,每个宏仍然打印参数,但由于macro3现在实际上返回源代码,它实际上像println.
请注意, 不testing-macro-2打印任何内容,因为macro2丢弃了中间计算的结果`(macro1 ~x)并简单地返回nil( 的结果println)。换句话说, using(macro2 foo)与nil在代码中放置文字相同,不同之处在于编译器foo在评估宏时将打印为副作用。
调用(macro3 hello)导致 aCompilerException因为宏替换导致代码(println hello),但hello未定义。如果您执行类似的操作,(def hello "Hello there!")则不会出现错误,因为它会找到hello.
| 归档时间: |
|
| 查看次数: |
2492 次 |
| 最近记录: |