没有命名空间的取消引用
Unquoting without namespace
我需要在没有命名空间的情况下引用并将其与取消引用结合起来。类似于:
'[a b ~c]
不幸的是,取消引用仅适用于句法引用:
`[a b ~c]
但随后扩展为
[user/a user/b 7]
我想在没有命名空间的情况下进行扩展。
clojurians slack channel 上的建议如下:
对符号使用 "quote unquote" 的组合以摆脱命名空间:
`[~'a ~'b ~c]
这很完美。
作为参考,我一直在研究一种类似的功能,它不需要像 ~'a
那样对每个您希望保持不变的符号进行防御性处理。它尚未发布,但这是技术:
;-----------------------------------------------------------------------------
(defn unquote-form?
[arg]
(and (list? arg)
(= (quote unquote) (first arg))))
(defn unquote-splicing-form?
[arg]
(and (list? arg)
(= (quote unquote-splicing) (first arg))))
(defn quote-template-impl
[form]
(walk/prewalk
(fn [item]
(cond
(unquote-form? item) (eval (xsecond item))
(sequential? item) (let [unquoted-vec (apply glue
(forv [it item]
(if (unquote-splicing-form? it)
(eval (xsecond it))
[it])))
final-result (if (list? item)
(t/->list unquoted-vec)
unquoted-vec)]
final-result)
:else item))
form))
(defmacro quote-template
[form]
(quote-template-impl form))
和单元测试以展示它的实际效果:
;-----------------------------------------------------------------------------
(def vec234 [2 3 4])
(dotest
(is (td/unquote-form? (quote (unquote (+ 2 3)))))
(is (td/unquote-splicing-form? (quote (unquote-splicing (+ 2 3)))))
(is= (td/quote-template {:a 1 :b (unquote (+ 2 3))})
{:a 1, :b 5})
(is= (td/quote-template {:a 1 :b (unquote (vec (range 3)))})
{:a 1, :b [0 1 2]})
(is= (td/quote-template {:a 1 :b (unquote vec234)})
{:a 1, :b [2 3 4]})
(let [result (td/quote-template (list 1 2 (unquote (inc 2)) 4 5))]
(is (list? result))
(is= result (quote (1 2 3 4 5))))
(is= (td/quote-template [1 (unquote-splicing vec234) 5]) ; unqualified name OK here
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote-splicing (t/thru 2 4)) 5])
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote (t/thru 2 4)) 5])
[1 [2 3 4] 5])
)
因此,您可以使用 quote-template
而不是 Clojure 的语法引号(即反引号)。然后,使用 unquote
或 unquote-splicing
将值插入到引用的模板中,而无需将名称空间添加到其他符号之前。
我知道这可能不是您正在寻找的答案,为什么不使用代码来构建您想要的东西呢?为什么要强迫它在一个表达式中完成?例如,您可以使用
(conj '[a b] c)
我需要在没有命名空间的情况下引用并将其与取消引用结合起来。类似于:
'[a b ~c]
不幸的是,取消引用仅适用于句法引用:
`[a b ~c]
但随后扩展为
[user/a user/b 7]
我想在没有命名空间的情况下进行扩展。
clojurians slack channel 上的建议如下:
对符号使用 "quote unquote" 的组合以摆脱命名空间:
`[~'a ~'b ~c]
这很完美。
作为参考,我一直在研究一种类似的功能,它不需要像 ~'a
那样对每个您希望保持不变的符号进行防御性处理。它尚未发布,但这是技术:
;-----------------------------------------------------------------------------
(defn unquote-form?
[arg]
(and (list? arg)
(= (quote unquote) (first arg))))
(defn unquote-splicing-form?
[arg]
(and (list? arg)
(= (quote unquote-splicing) (first arg))))
(defn quote-template-impl
[form]
(walk/prewalk
(fn [item]
(cond
(unquote-form? item) (eval (xsecond item))
(sequential? item) (let [unquoted-vec (apply glue
(forv [it item]
(if (unquote-splicing-form? it)
(eval (xsecond it))
[it])))
final-result (if (list? item)
(t/->list unquoted-vec)
unquoted-vec)]
final-result)
:else item))
form))
(defmacro quote-template
[form]
(quote-template-impl form))
和单元测试以展示它的实际效果:
;-----------------------------------------------------------------------------
(def vec234 [2 3 4])
(dotest
(is (td/unquote-form? (quote (unquote (+ 2 3)))))
(is (td/unquote-splicing-form? (quote (unquote-splicing (+ 2 3)))))
(is= (td/quote-template {:a 1 :b (unquote (+ 2 3))})
{:a 1, :b 5})
(is= (td/quote-template {:a 1 :b (unquote (vec (range 3)))})
{:a 1, :b [0 1 2]})
(is= (td/quote-template {:a 1 :b (unquote vec234)})
{:a 1, :b [2 3 4]})
(let [result (td/quote-template (list 1 2 (unquote (inc 2)) 4 5))]
(is (list? result))
(is= result (quote (1 2 3 4 5))))
(is= (td/quote-template [1 (unquote-splicing vec234) 5]) ; unqualified name OK here
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote-splicing (t/thru 2 4)) 5])
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote (t/thru 2 4)) 5])
[1 [2 3 4] 5])
)
因此,您可以使用 quote-template
而不是 Clojure 的语法引号(即反引号)。然后,使用 unquote
或 unquote-splicing
将值插入到引用的模板中,而无需将名称空间添加到其他符号之前。
我知道这可能不是您正在寻找的答案,为什么不使用代码来构建您想要的东西呢?为什么要强迫它在一个表达式中完成?例如,您可以使用
(conj '[a b] c)