作为练习,我想实现Doug Hoytes "Let over Lambda"中描述的一些宏,这些宏我已经读过一段时间了。
当我开始玩anaphoric macros时,遇到了一个奇怪的问题。我已经实现了本书中描述的alet
宏,如下所示:
(defmacro a-let
"Anaphoric let, `this` refers to the last form in body, which should be a
function"
[bindings & body]
`(let [~'this (atom nil) ~@bindings]
(reset! ~'this ~(last body))
~@(butlast body)
(fn [& params]
(apply ~'@this params))))
这可以编译。但是,如果我尝试在代码中使用它,例如在此简单示例中
(a-let [a 1, b 2]
(fn [] (+ a b)))
苹果酒抗议并在下面的堆栈跟踪中引发错误:
2. Unhandled clojure.lang.Compiler$CompilerException
Error compiling /home/dasbente/Dokumente/Informatik/Clojure/let-over-lambda.clj at (39:1)
Compiler.java: 6891 clojure.lang.Compiler/checkSpecs
Compiler.java: 6907 clojure.lang.Compiler/macroexpand1
Compiler.java: 6989 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 6729 clojure.lang.Compiler/analyze
Compiler.java: 6100 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 6420 clojure.lang.Compiler$LetExpr$Parser/parse
Compiler.java: 7003 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 6729 clojure.lang.Compiler/analyze
Compiler.java: 6100 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 5460 clojure.lang.Compiler$FnMethod/parse
Compiler.java: 4022 clojure.lang.Compiler$FnExpr/parse
Compiler.java: 7001 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 7059 clojure.lang.Compiler/eval
Compiler.java: 7025 clojure.lang.Compiler/eval
core.clj: 3206 clojure.core/eval
core.clj: 3202 clojure.core/eval
main.clj: 243 clojure.main/repl/read-eval-print/fn
main.clj: 243 clojure.main/repl/read-eval-print
main.clj: 261 clojure.main/repl/fn
main.clj: 261 clojure.main/repl
main.clj: 177 clojure.main/repl
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 87 clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
core.clj: 657 clojure.core/apply
core.clj: 1965 clojure.core/with-bindings*
core.clj: 1965 clojure.core/with-bindings*
RestFn.java: 425 clojure.lang.RestFn/invoke
interruptible_eval.clj: 85 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 55 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 222 clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
interruptible_eval.clj: 190 clojure.tools.nrepl.middleware.interruptible-eval/run-next/fn
AFn.java: 22 clojure.lang.AFn/run
ThreadPoolExecutor.java: 1149 java.util.concurrent.ThreadPoolExecutor/runWorker
ThreadPoolExecutor.java: 624 java.util.concurrent.ThreadPoolExecutor$Worker/run
Thread.java: 748 java.lang.Thread/run
1. Caused by clojure.lang.ExceptionInfo
Call to clojure.core/fn did not conform to spec: In: [0 1] val:
user/params fails spec: :clojure.core.specs.alpha/local-name at:
[:args :bs :arity-1 :args :varargs :form :sym] predicate:
simple-symbol? In: [0 1] val: user/params fails spec:
:clojure.core.specs.alpha/seq-binding-form at: [:args :bs :arity-1
:args :varargs :form :seq] predicate: vector? In: [0 1] val:
user/params fails spec: :clojure.core.specs.alpha/map-bindings at:
[:args :bs :arity-1 :args :varargs :form :map] predicate: coll?
In: [0 1] val: user/params fails spec:
:clojure.core.specs.alpha/map-special-binding at: [:args :bs
:arity-1 :args :varargs :form :map] predicate: map? In: [0 0] val:
& fails spec: :clojure.core.specs.alpha/arg-list at: [:args :bs
:arity-n :args] predicate: vector?
#:clojure.spec.alpha{:problems
({:path
[:args :bs :arity-1 :args :varargs :form :sym],
:pred clojure.core/simple-symbol?,
:val user/params,
:via
[:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/local-name],
:in [0 1]}
{:path
[:args :bs :arity-1 :args :varargs :form :seq],
:pred clojure.core/vector?,
:val user/params,
:via
[:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/seq-binding-form],
:in [0 1]}
{:path
[:args :bs :arity-1 :args :varargs :form :map],
:pred clojure.core/coll?,
:val user/params,
:via
[:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/map-binding-form
:clojure.core.specs.alpha/map-bindings],
:in [0 1]}
{:path
[:args :bs :arity-1 :args :varargs :form :map],
:pred map?,
:val user/params,
:via
[:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/binding-form
:clojure.core.specs.alpha/map-binding-form
:clojure.core.specs.alpha/map-special-binding],
:in [0 1]}
{:path [:args :bs :arity-n :args],
:pred clojure.core/vector?,
:val &,
:via
[:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/args+body
:clojure.core.specs.alpha/arg-list
:clojure.core.specs.alpha/arg-list],
:in [0 0]}),
:spec
#object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x3517c752 "clojure.spec.alpha$regex_spec_impl$reify__2436@3517c752"],
:value
([& user/params]
(clojure.core/apply @this user/params)),
:args
([& user/params]
(clojure.core/apply @this user/params))}
core.clj: 4739 clojure.core/ex-info
core.clj: 4739 clojure.core/ex-info
alpha.clj: 689 clojure.spec.alpha/macroexpand-check
alpha.clj: 681 clojure.spec.alpha/macroexpand-check
AFn.java: 156 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
Var.java: 702 clojure.lang.Var/applyTo
Compiler.java: 6889 clojure.lang.Compiler/checkSpecs
Compiler.java: 6907 clojure.lang.Compiler/macroexpand1
Compiler.java: 6989 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 6729 clojure.lang.Compiler/analyze
Compiler.java: 6100 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 6420 clojure.lang.Compiler$LetExpr$Parser/parse
Compiler.java: 7003 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 6729 clojure.lang.Compiler/analyze
Compiler.java: 6100 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 5460 clojure.lang.Compiler$FnMethod/parse
Compiler.java: 4022 clojure.lang.Compiler$FnExpr/parse
Compiler.java: 7001 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6773 clojure.lang.Compiler/analyze
Compiler.java: 7059 clojure.lang.Compiler/eval
Compiler.java: 7025 clojure.lang.Compiler/eval
core.clj: 3206 clojure.core/eval
core.clj: 3202 clojure.core/eval
main.clj: 243 clojure.main/repl/read-eval-print/fn
main.clj: 243 clojure.main/repl/read-eval-print
main.clj: 261 clojure.main/repl/fn
main.clj: 261 clojure.main/repl
main.clj: 177 clojure.main/repl
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 87 clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
core.clj: 657 clojure.core/apply
core.clj: 1965 clojure.core/with-bindings*
core.clj: 1965 clojure.core/with-bindings*
RestFn.java: 425 clojure.lang.RestFn/invoke
interruptible_eval.clj: 85 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 55 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 222 clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
interruptible_eval.clj: 190 clojure.tools.nrepl.middleware.interruptible-eval/run-next/fn
AFn.java: 22 clojure.lang.AFn/run
ThreadPoolExecutor.java: 1149 java.util.concurrent.ThreadPoolExecutor/runWorker
ThreadPoolExecutor.java: 624 java.util.concurrent.ThreadPoolExecutor$Worker/run
Thread.java: 748 java.lang.Thread/run
这还不是很奇怪,编写宏或类似的东西时会发生错误。但是,当我使用macroexpand-1
扩展宏时,结果代码没有任何问题:
(macroexpand-1 '(a-let [a 1, b 2]
(fn [] (+ a b))))
;; => (clojure.core/let [this (clojure.core/atom nil) a 1 b 2]
;; (clojure.core/reset! this (fn [] (+ a b)))
;; (clojure.core/fn [& user/params] (clojure.core/apply (clojure.core/deref this) user/params)))
;; Without namespaces for readability
;; => (let [this (atom nil) a 1 b 2]
;; (reset! this (fn [] (+ a b)))
;; (fn [& params] (apply @this params)))
在macroexpand-1
之外,哪个也可以正常工作
(def f *) ;; => #'user/f
(f) ;; => 1
我对Clojures宏系统的细节并不熟悉,因此很高兴被启发了解为什么会发生这种怪异的行为,因为我对此非常迷惑。
预先感谢!
答案 0 :(得分:1)
我不确定那条错误消息是从哪里来的。我不能说我以前见过这样的错误。
运行它,我得到:
CompilerException java.lang.RuntimeException: Can't use qualified name as parameter: mandelbrot-redo.seesaw-main.first-main/params, compiling:(C:\Users\slomi\AppData\Local\Temp\form-init395175488607706237.clj:1:1)
然后该错误显而易见。在`
引号形式内创建绑定时,它们会自动命名空间到当前名称空间。如错误所示,函数参数不能命名空间。
将最后一位更改为:
(fn [& params#]
(apply ~'@this params#))))
请注意#
。那些将params
变成一个唯一的,没有命名空间的符号。
现在,它似乎可以正常工作:
(a-let [a 1, b 2]
(fn [] (+ a b)))
=>
#object[mandelbrot_redo.seesaw_main.first_main$eval8162$fn__8165
0x63cf5b7e
"mandelbrot_redo.seesaw_main.first_main$eval8162$fn__8165@63cf5b7e"]
您也可以使用promise
代替atom
。稍微整洁,更正确,因为您只想设置一次:
(defmacro my-a-let
"Anaphoric let, `this` refers to the last form in body, which should be a function"
[bindings & body]
`(let [~'this (promise)
~@bindings]
(deliver ~'this ~(last body))
~@(butlast body)
(fn [& params#]
(apply ~'@this params#))))