我的问题是:如何获取已接收函数的args列表和表达式?
我正在尝试做这样的事情:
(defn first-fn [[args exprs]]
(println "Args:" args)
(println "Exprs:" exprs))
(first-fn (fn [a b c] (println "something")))
所以,first-fn
会打印出来:
Args: [a b c]
Exprs: (println "something")
我的目标是创建一个可以使用已接收函数的args列表的宏。
谢谢。
编辑:
用例:
我正在使用compojure https://github.com/weavejester/compojure
您可以定义如下路线:
(GET "/:id" [id] (body_here id))
但我想将语法更改为:
(defn handler-fn [id] (body_here id))
...
(GET "/:id" handler-fn)
因此可以从路由中提取处理程序(正文),也可以重用它。
我尝试重用compile-route https://github.com/weavejester/compojure/blob/master/src/compojure/core.clj#L172
(defmacro MY_GET [path fn-src]
(let [fn-fn (second fn-src)
arg-vec (nth fn-src 2)
forms (drop 3 fn-src)]
(compojure.core/compile-route :get path arg-vec forms)))
但是当我打电话时:
(MY_GET "/:id" handler-fn)
它说:Don't know how to create ISeq from: clojure.lang.Symbol
答案 0 :(得分:1)
你无法通过函数执行此操作,直接需要一个宏来执行此操作,即使这样也不是直截了当的。首先,让我们解释一下差异:宏基本上是在编译时评估的,然后在运行时评估此评估的结果。有趣的是,编译时的评估将宏的文字,未评估的参数作为数据获取,而不像正常函数那样,在运行时获得评估的参数。因此,您的方法无法工作,因为当first-fn
收到它的参数(在运行时)时,它们已经被评估 - 在您的示例中,first-fn
接收nil
作为参数。参看documentation at clojure-doc以获得更好的解释。
现在,使用宏来解决您的请求需要宏来解析它接收的参数(记住:在编译时,代码是数据) - 即在您的示例中,它需要解析序列(fn [a b c] (println "something"))
这会建立你交给它的函数调用。可能你想要覆盖fn
之外的其他情况(例如#short-hand),这就是它在一般情况下使问题不直接的原因。
这种解析最终可以通过正常的函数解析来处理,例如,一个序列。所以,首先尝试解决一个不同的难题:构建一个函数parse-code-sequence
,它接受一个序列(看起来像你要移交的函数)并返回args和expr - 注意引用('
)在fn
前面。
user> (parse-code-sequence '(fn [a b c] (println "something")))
{args: [a b c], expr: (println "something")}
这方面的一些提示:在这里显示最常用情况的示例中,序列只包含三个元素,而您不需要第一个元素。但一般情况稍微复杂一些,参见official documentation on fn
。
最后一句话:当你实现宏时,你需要考虑它解决的问题 - 只是添加print-statements很容易,但是你也想要正常评估参数(所以你的宏会变成类似的东西)调试辅助工具)或者你想做别的事情吗?
更新以反映您的用例
您的MY-GET
宏未执行您认为正在执行的操作。
看一下宏得到的参数:为什么你认为它可以神奇地检索handler-fn
的函数定义,当你作为MY_GET
的参数提供的所有内容都是symbol / var handler-fn
?你需要检索来源,但这通常是不可能的(参见retrieving the source of a function definition上的这个问题)。
在调用compile-route
之前,您还缺少一个反引号:您希望在运行时调用compile-route
,而不是在编译时。目前,宏评估的结果是调用compile-route
(在编译时)的结果。看看macroexpand
,它会显示宏扩展的结果。基本上,您希望宏将呼叫返回到compile-route
。
我认为没有任何简单的方法可以实现您的目标。路径定义的参数向量定义了需要移交的内容。即使你将它提取到一个函数定义,compojure仍然需要知道将什么交给该函数。
答案 1 :(得分:0)
以下是您可以做的一个示例。
(ns xyz
(:require
[tupelo.core :as t]
))
(t/refer-tupelo)
(spyx *clojure-version*)
(defmacro dissect [ fn-src ]
(let [fn-fn (first fn-src)
arg-vec (second fn-src)
forms (drop 2 fn-src) ]
(spyx fn-fn)
(spyx arg-vec)
(spyx forms)
; Here is the return value; ie the transformed code
`(defn my-fn
~arg-vec
(apply + ~arg-vec))))
; show the result
(newline)
(println
(macroexpand-1
'(dissect
(fn [a b c]
(println "the answer is")
42))))
; call it for real
(newline)
(dissect
(fn [a b c]
(println "the answer is")
42))
; use the generated function
(newline)
(spyx (my-fn 1 2 3))
结果:
*clojure-version* => {:major 1, :minor 8, :incremental 0, :qualifier nil}
fn-fn => fn
arg-vec => [a b c]
forms => ((println "the answer is") 42)
(clojure.core/defn tst.clj.core/my-fn [a b c] (clojure.core/apply clojure.core/+ [a b c]))
fn-fn => fn
arg-vec => [a b c]
forms => ((println "the answer is") 42)
(my-fn 1 2 3) => 6
您的project.clj需要以下内容才能使spyx
正常工作:
:dependencies [
[tupelo "0.9.11"]