帮我写一个Clojure宏,它自动将元数据添加到函数定义中

时间:2021-05-17 22:47:53

I realize that the first rule of Macro Club is Don't Use Macros, so the following question is intended more as an exercise in learning Clojure than anything else (I realize this isn't necessarily the best use of macros).

我意识到Macro Club的第一条规则是不要使用宏,所以下面的问题更多的是作为学习Clojure的练习而不是其他任何东西(我意识到这不一定是宏的最佳用法)。

I want to write a simple macro which acts as a wrapper around a regular (defn) macro and winds up adding some metadata to the defined function. So I'd like to have something like this:

我想编写一个简单的宏,它充当常规(defn)宏的包装器,并最终向定义的函数添加一些元数据。所以我想要这样的东西:

(defn-plus f [x] (inc x))

...expand out to something like this:

......扩展到这样的东西:

(defn #^{:special-metadata :fixed-value} f [x] (inc x))

In principle this doesn't seem that hard to me, but I'm having trouble nailing down the specifics of getting the [args] and other forms in the defined function to be parsed out correctly.

原则上这对我来说似乎并不难,但是我无法确定正确解析定义函数中的[args]和其他形式的具体细节。

As a bonus, if possible I'd like the macro to be able to handle all of the disparate forms of defn (ie, with or without docstrings, multiple arity definitions, etc). I saw some things in the clojure-contrib/def package that looked possibly helpful, but it was difficult to find sample code which used them.

作为奖励,如果可能的话,我希望宏能够处理所有不同形式的defn(即,有或没有docstrings,多个arity定义等)。我在clojure-contrib / def包中看到了一些看起来可能有用的东西,但是很难找到使用它们的示例代码。

1 个解决方案

#1


Updated:

The previous version of my answer was not very robust. This seems like a simpler and more proper way of doing it, stolen from clojure.contrib.def:

我的答案的先前版本不是很强大。这似乎是一种更简单,更正确的方法,从clojure.contrib.def中窃取:

(defmacro defn-plus [name & syms]
  `(defn ~(vary-meta name assoc :some-key :some-value) ~@syms))

user> (defn-plus ^Integer f "Docstring goes here" [x] (inc x))
#'user/f
user> (meta #'f)
{:ns #<Namespace user>, :name f, :file "NO_SOURCE_PATH", :line 1, :arglists ([x]), :doc "Docstring goes here", :some-key :some-value, :tag java.lang.Integer}

#^{} and with-meta are not the same thing. For an explanation of the difference between them, see Rich's discussion on the Clojure mailing list. It's all a bit confusing and it's come up a bunch of times on the mailing list; see also here for example.

#^ {}和with-meta不是一回事。有关它们之间差异的解释,请参阅Rich对Clojure邮件列表的讨论。这有点令人困惑,它在邮件列表上出现了很多次;另见这里的例子。

Note that def is a special form and it handles metadata a bit oddly compared with some other parts of the language. It sets the metadata of the var you're deffing to the metadata of the symbol that names the var; that's the only reason the above works, I think. See the DefExpr class in Compiler.java in the Clojure source if you want to see the guts of it all.

请注意,def是一种特殊形式,与语言的其他部分相比,它处理元数据有点奇怪。它将您正在设置的var的元数据设置为命名var的符号的元数据;我认为这是上述工作的唯一原因。如果你想看到它的内容,请参阅Clojure源代码中的Compiler.java中的DefExpr类。

Finally, page 216 of Programming Clojure says:

最后,Programming Clojure的第216页说:

You should generally avoid reader macros in macro expansions, since reader macros are evaluated at read time, before macro expansion begins.

您通常应该避免宏扩展中的读取器宏,因为在宏扩展开始之前,读取器宏在读取时进行评估。

#1


Updated:

The previous version of my answer was not very robust. This seems like a simpler and more proper way of doing it, stolen from clojure.contrib.def:

我的答案的先前版本不是很强大。这似乎是一种更简单,更正确的方法,从clojure.contrib.def中窃取:

(defmacro defn-plus [name & syms]
  `(defn ~(vary-meta name assoc :some-key :some-value) ~@syms))

user> (defn-plus ^Integer f "Docstring goes here" [x] (inc x))
#'user/f
user> (meta #'f)
{:ns #<Namespace user>, :name f, :file "NO_SOURCE_PATH", :line 1, :arglists ([x]), :doc "Docstring goes here", :some-key :some-value, :tag java.lang.Integer}

#^{} and with-meta are not the same thing. For an explanation of the difference between them, see Rich's discussion on the Clojure mailing list. It's all a bit confusing and it's come up a bunch of times on the mailing list; see also here for example.

#^ {}和with-meta不是一回事。有关它们之间差异的解释,请参阅Rich对Clojure邮件列表的讨论。这有点令人困惑,它在邮件列表上出现了很多次;另见这里的例子。

Note that def is a special form and it handles metadata a bit oddly compared with some other parts of the language. It sets the metadata of the var you're deffing to the metadata of the symbol that names the var; that's the only reason the above works, I think. See the DefExpr class in Compiler.java in the Clojure source if you want to see the guts of it all.

请注意,def是一种特殊形式,与语言的其他部分相比,它处理元数据有点奇怪。它将您正在设置的var的元数据设置为命名var的符号的元数据;我认为这是上述工作的唯一原因。如果你想看到它的内容,请参阅Clojure源代码中的Compiler.java中的DefExpr类。

Finally, page 216 of Programming Clojure says:

最后,Programming Clojure的第216页说:

You should generally avoid reader macros in macro expansions, since reader macros are evaluated at read time, before macro expansion begins.

您通常应该避免宏扩展中的读取器宏,因为在宏扩展开始之前,读取器宏在读取时进行评估。