clojure得到args&已接收函数的exprs

时间:2016-12-09 10:21:02

标签: clojure

我的问题是:如何获取已接收函数的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

2 个答案:

答案 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宏未执行您认为正在执行的操作。

  1. 看一下宏得到的参数:为什么你认为它可以神奇地检索handler-fn的函数定义,当你作为MY_GET的参数提供的所有内容都是symbol / var handler-fn?你需要检索来源,但这通常是不可能的(参见retrieving the source of a function definition上的这个问题)。

  2. 在调用compile-route之前,您还缺少一个反引号:您希望在运行时调用compile-route,而不是在编译时。目前,宏评估的结果是调用compile-route(在编译时)的结果。看看macroexpand,它会显示宏扩展的结果。基本上,您希望宏将呼叫返回到compile-route

  3. 我认为没有任何简单的方法可以实现您的目标。路径定义的参数向量定义了需要移交的内容。即使你将它提取到一个函数定义,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"]