【问题标题】:Can't get Clojure macro to execute without expansion error无法在没有扩展错误的情况下执行 Clojure 宏
【发布时间】:2021-05-06 18:37:15
【问题描述】:

我正在编写一个宏,它会查看给定符号的元数据并删除任何不是关键字的条目,即键名不以“:”开头,例如

(meta (var X))  ;; Here's the metadata for testing...
=>
{:line 1,
 :column 1,
 :file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init11598934441516564808.clj",
 :name X,
 :ns #object[clojure.lang.Namespace 0x12ed80f6 "thic.core"],
 OneHundred 100,
 NinetyNine 99}

我想删除条目“OneHundred”和“NinetyNine”,而保留其余元数据不变。

所以我有一些有效的代码:

    (let [Hold# (meta (var X))]  ;;Make a copy of the metadata to search.
      (map (fn [[kee valu]]      ;;Loop through each metadata key/value.
        (if
          (not= \: (first (str kee)))  ;; If we find a non-keyword key,
          (reset-meta! (var X) (dissoc (meta (var X)) kee))  ;; remove it from X's metadata.
          )
        )
       Hold#  ;;map through this copy of the metadata.
      )
    )

它有效。 “OneHundred”和“NinetyNine”的条目从 X 的元数据中消失了。

然后我把它编码成一个宏。上帝保佑 REPL。

(defmacro DelMeta! [S]  
  `(let [Hold# (meta (var ~S))] ;; Hold onto a copy of S's metadata.
     (map                       ;; Scan through the copy looking for keys that DON'T start with ":"
       (fn [[kee valu]]
         (if                    ;; If we find metadata whose keyname does not start with a ":"
           (not= \: (first (str kee)))
           (reset-meta! (var ~S) (dissoc (meta (var ~S)) kee))  ;; remove it from S's metadata.
           )
         )
       Hold#        ;; Loop through the copy of S's metadata so as to not confuse things.
       )
     )
  )

使用 defmacro 定义宏不会出错。
宏上的macroexpand-1,例如

(macroexpand-1 '(DelMeta! X))

扩展为正确的代码。这里:

(macroexpand-1 '(DelMeta! X))
=>
(clojure.core/let
 [Hold__2135__auto__ (clojure.core/meta (var X))]
 (clojure.core/map
  (clojure.core/fn
   [[thic.core/kee thic.core/valu]]
   (if
    (clojure.core/not= \: (clojure.core/first (clojure.core/str thic.core/kee)))
    (clojure.core/reset-meta! (var X) (clojure.core/dissoc (clojure.core/meta (var X)) thic.core/kee))))
  Hold__2135__auto__))

但是!!!

实际上在 REPL 中使用实参调用宏会发出最难以理解的错误消息:

(DelMeta! X)  ;;Invoke DelMeta! macro with symbol X.

Syntax error macroexpanding clojure.core/fn at (C:\Users\Joe User\AppData\Local\Temp\form-init11598934441516564808.clj:1:1).
([thic.core/kee thic.core/valu]) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
(thic.core/kee thic.core/valu) - failed: Extra input at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

哦,全能而睿智的Clojuregods,我恳求你怜悯。 我的罪在哪里?

【问题讨论】:

  • 我开始写一个答案,但停止了,因为它真的不清楚你到底想达到什么目标。一些提示:我没有得到相同的异常,而是得到一个异常,告诉我“kee”不存在——你想使用kee#(如果你想使用它,valu#)。阅读变量捕获,例如在Clojure for the Brave and True。其次,当您想要的只是副作用时,您调用map,使用doseq 更清楚地表达了这一点。但主要问题是,为什么您首先会有非关键字元数据?
  • map 在这里是错误的,但doseq 也是如此,因为两个更新会相互干扰。而是使用reduceinto/for 在功能上构建新地图,然后设置一次。
  • 我觉得结尾很有趣哈哈

标签: intellij-idea clojure metadata cursive


【解决方案1】:

这里不需要宏。此外,您误解了 Clojure keyword 的性质,以及 Clojure Var 与局部变量的复杂性。

通过在 let 块中使用本地“变量”而不是 Var 来保持简单:

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

(dotest
  (let [x  (with-meta [1 2 3] {:my "meta"})
        x2 (vary-meta x assoc :your 25 'abc :def)
        x3 (vary-meta x2 dissoc 'abc )]
    (is= x  [1 2 3])
    (is= x2 [1 2 3])
    (is= x3 [1 2 3])

    (is= (meta x)  {:my "meta"})
    (is= (meta x2) {:my "meta", :your 25, 'abc :def})
    (is= (meta x3) {:my "meta", :your 25}))

所以我们看到 x、x2 和 x3 的值是常数。这就是元数据的目的。第二组测试显示了使用vary-meta对元数据的影响,这是更改值的最佳方法。

当我们使用 Var 时,它不仅是一个全局值,而且就像 C 中的指针的双重间接。请看这个问题:

这个答案还阐明了字符串、符号和关键字之间的区别。这很重要。

考虑这段代码

(def ^{:my "meta"} data [1 2 3])
(spyx data)
(spyx-pretty (meta (var data)))

结果:

data => [1 2 3]

(meta (var data)) => 
    {:my "meta",
     :line 19,
     :column 5,
     :file "tst/demo/core.cljc",
     :name data,
     :ns #object[clojure.lang.Namespace 0x4e4a2bb4 "tst.demo.core"]}

(is= data [1 2 3])
(is= (set (keys (meta (var data))))
  #{:my :line :column :file :name :ns})

因此,我们已根据需要将密钥 :my 添加到元数据中。我们怎样才能改变它? 对于 Var,使用函数 alter-meta!

(alter-meta! (var data) assoc :your 25 'abc :def)
(is= (set (keys (meta (var data))))
  #{:ns :name :file 'abc :your :column :line :my})

所以我们在元数据映射中添加了 2 个新条目。一个以关键字:your 作为键,值为25,另一个以符号abc 作为键,值为:def(关键字)。

我们还可以使用alter-meta! 从元数据映射中远程获取键/值对:

(alter-meta! (var data) dissoc 'abc )
(is= (set (keys (meta (var data))))
  #{:ns :name :file :your :column :line :my})

关键字 vs 符号 vs 字符串

字符串文字在源文件中在每一端都有双引号,但它们不是字符串中的字符。类似地,源文件中的关键字文字 需要一个前导冒号来标识它。但是,字符串的双引号和关键字的冒号都不是该值的name 的一部分。

因此,您无法通过冒号识别关键字。您应该使用这些函数来识别不同的数据类型:

以上来自Clojure CheatSheet。所以,你真正想要的代码是:

(defn remove-metadata-symbol-keys
  [var-obj]
  (assert (var? var-obj)) ; verify it is a Var
  (doseq [k (keys (meta var-obj))]
    (when (not (keyword? k))
      (alter-meta! var-obj dissoc k))))

有一个样本:

(def ^{:some "stuff" 'other :things} myVar [1 2 3])
(newline) (spyx-pretty (meta (var myVar)))

(remove-metadata-symbol-keys (var myVar))

(newline) (spyx-pretty (meta (var myVar)))

结果:

(meta (var myVar)) => 
{:some "stuff",
 other :things,          ; *** to be removed ***
 :line 42,
 :column 5,
 :file "tst/demo/core.cljc",
 :name myVar,
 :ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}


(meta (var myVar)) =>   ; *** after removing non-keyword keys ***
{:some "stuff",
 :line 42,
 :column 5,
 :file "tst/demo/core.cljc",
 :name myVar,
 :ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}

以上代码都是使用this template project运行的。

【讨论】:

  • Alan Thompson:你以前回答过我的问题,我想我希望你在这里再次回答。而你做到了! 600字! :) 非常感谢。让我通过它,尝试你的样品,然后回复你。金星,欧比旺。
  • 好的。阅读。几个月前,我花了一段时间来理解 Symbol -> var -> 价值链。我经常想象 Clojure 在“幕后”使用一些双重间接。感谢您确认。我的 Clojure 初学者知识不包括“键”功能或“关键字?”功能。让我玩它,我会回答更多问题,或者希望不会。 :)
猜你喜欢
  • 2019-02-10
  • 2021-04-29
  • 1970-01-01
  • 1970-01-01
  • 2017-07-31
  • 1970-01-01
  • 2011-08-01
  • 2020-12-16
  • 2014-03-03
相关资源
最近更新 更多