【问题标题】:Why is this code using multiple types of quotes为什么这段代码使用多种类型的引号
【发布时间】:2019-08-18 22:30:02
【问题描述】:

来自:Clojure 的乐趣

(defn contextual-eval [ctx expr]
  (eval
   `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)]
      ~expr)))

现在如果我打电话

(contextual-eval '{a 1 b 2} '(+ a b))

它按预期返回 3

但是为什么使用 `'~v 而不仅仅是 v 呢?

如果我删除 eval.. 使函数看起来像这样

(defn contextual-eval [ctx expr]
  `(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
    ~expr))

然后调用

(eval (contextual-eval '{a 1 b 2} '(+ a b)))

它仍然按预期返回 3

所以我不确定为什么 `'~v 在 eval 里面时使用 函数体。

【问题讨论】:

  • 哪个版本和页码?
  • 第二版,第 176 页 - 第 8 章宏 抱歉,在第二个示例中,我忘记删除 eval。所以修好了

标签: clojure macros quote


【解决方案1】:

我认为这是一个错字。这段代码显示了正在发生的事情,以及应该发生的事情:

(defn contextual-eval [ctx expr]
  (spy :01 ctx)

  (spy :02 (mapcat (fn [[k v]] [k `'~v]) ctx))
  (spy :03 `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)] ~expr) )

  (spy :02a (mapcat (fn [[k v]] [k v]) ctx))
  (spy :03a `(let [~@(mapcat (fn [[k v]] [k v]) ctx)] ~expr) )

  (eval `(let [~@(mapcat (fn [[k v]] [k v]) ctx)] ~expr) )
  )

(dotest
  (nl)
  (spy :10
    (contextual-eval '{a 1 b 2}
      '(+ a b)))
  (nl)
  (spy :20
    (contextual-eval '{a 1
                       b (+ a 3)}
      '(+ a b)))
  )

我们得到了第一次运行的结果:

:01 => {a 1, b 2}

:02 => (a (quote 1) b (quote 2))
:03 => (clojure.core/let [a (quote 1) b (quote 2)] (+ a b))

:02a => (a 1 b 2)
:03a => (clojure.core/let [a 1 b 2] (+ a b))

:10 => 3

第二次运行:

:01 => {a 1, b (+ a 3)}

:02 => (a (quote 1) b (quote (+ a 3)))
:03 => (clojure.core/let [a (quote 1) b (quote (+ a 3))] (+ a b))

:02a => (a 1 b (+ a 3))
:03a => (clojure.core/let [a 1 b (+ a 3)] (+ a b))

:20 => 5

通过将“`'~v”转换为“v”,它会做它应该做的事情。


附:虽然 JoC 有很多好东西,但它非常先进,应该更像你的第五本 Clojure 书,而不是第一本(这是我的第一本 Clojure 书,我很困惑)。从以下开始,您会得到很好的服务:

  • 获取 Clojure
  • 活的 Clojure
  • 勇敢而真实的 Clojure

更新:

我会做一些不同的事情。真的不需要mapcat。注意(ns ...) 表单中的(:use ...) 子句:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)) 

(defn contextual-eval [ctx expr]
  (spy :01 ctx)
  (spy :02 (seq ctx))
  (spy :03 (vec (apply concat (seq ctx))))
  (spy :04 `(let ~(vec (apply concat (seq ctx)))
              ~expr))
  (eval `(let ~(vec (apply concat (seq ctx)))
           ~expr)))

(dotest
  (nl)
  (spy :100
    (contextual-eval '{a 1 b 2}
      '(+ a b)))
  (nl)
  (spy :200
    (contextual-eval '{a 1
                       b (+ a 3)}
      '(+ a b)))
  )

结果

:01 => {a 1, b 2}
:02 => ([a 1] [b 2])
:03 => [a 1 b 2]
:04 => (clojure.core/let [a 1 b 2] (+ a b))
:100 => 3

:01 => {a 1, b (+ a 3)}
:02 => ([a 1] [b (+ a 3)])
:03 => [a 1 b (+ a 3)]
:04 => (clojure.core/let [a 1 b (+ a 3)] (+ a b))
:200 => 5

project.clj 中,您需要向the Tupelo library 添加一个依赖项

[tupelo "0.9.138"]

更新 #2:

@amalloy 是正确的,他的示例有效。但是,它会因原始示例而失败:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(defn contextual-eval [ctx expr]
  (println :01 ctx)
  (println :02 (seq ctx))
  (println :04a (map (fn [[k v]] [k `'~v]) ctx))
  (println :04b  (mapcat (fn [[k v]] [k `'~v]) ctx))
  (println :05 `(let [~@(mapcat (fn [[k v]]
                                  [k `'~v]) ctx)]
                  ~expr))
  (spy :99 (eval
             `(let [~@(mapcat (fn [[k v]]
                                [k `'~v]) ctx)]
                ~expr))))

(dotest
  (newline)
  (throws?
    (println :100
      (contextual-eval '{a 2, b 5}
        '(- (first a) b))))

  (newline)
  (is= -4
    (let [a (range 5)
          b (last a)]
      (spy :300
        (contextual-eval {'a a, 'b b}
          '(- (first a) b)))))  )

结果:

:01 {a 2, b 5}
:02 ([a 2] [b 5])
:04a ([a (quote 2)] [b (quote 5)])
:04b (a (quote 2) b (quote 5))
:05 (clojure.core/let [a (quote 2) b (quote 5)] (- (first a) b))

:01 {a (0 1 2 3 4), b 4}
:02 ([a (0 1 2 3 4)] [b 4])
:04a ([a (quote (0 1 2 3 4))] [b (quote 4)])
:04b (a (quote (0 1 2 3 4)) b (quote 4))
:05 (clojure.core/let [a (quote (0 1 2 3 4)) b (quote 4)] (- (first a) b))
:99 => -4
:300 => -4

第一次尝试失败并出现异常:

  actual: java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long

所以,我想说这种脆弱的东西可能不是在 Clojure 中教授引用/取消引用的最佳示例。此外,除非您正在编写编译器或类似的代码,否则很少需要这样的代码。而且,如果您确实需要如此复杂且容易出错的东西,我建议您添加更多检查,分散步骤并显示中间结果,以帮助读者理解和维护人员调试。

【讨论】:

  • 你是对的。我想当我早些时候尝试只使用 v 时,它给出了一个错误。但是现在我再次尝试,代码没有看起来很滑稽的 `'~v。非常感谢您的回复。我能问一下你代码中的 spy 做了什么吗,因为当我尝试粘贴你的代码时它给了我一个错误。
  • 再次感谢您。我会听取你对其他书籍的建议,然后再回到“Clojure 的乐趣”……一旦我对它足够熟悉了。
  • 这不是错字。引用很重要。尝试在您的地图中使用更复杂的数据:例如,(let [a (range 5), b (last a)] (contextual-eval {'a a, 'b b} '(- (first a) b)))。书中的版本有效,而您的版本将失败。
  • @amalloy:谢谢。我想我需要真正理解整个宏引用正确..这对我来说有点黑魔法:)
  • @Alan Thompson:感谢您花时间尝试所有这些示例。当我对宏有更好的理解时,我会重新审视这个:)
猜你喜欢
  • 2015-04-12
  • 1970-01-01
  • 2017-04-20
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
  • 2014-11-06
  • 2011-09-16
相关资源
最近更新 更多