【问题标题】:clojure get args & exprs of received functionclojure 获取接收函数的参数和表达式
【发布时间】:2016-12-09 10:21:01
【问题描述】:

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

所以处理程序(主体)可以从路由中提取出来,也可以被重用。

我尝试重用编译路由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

【问题讨论】:

  • 你能举一个用例的例子吗?这可能有助于澄清目标/方法。

标签: clojure


【解决方案1】:

你不能用函数做到这一点,你直接需要一个宏来做到这一点,即使这样也不是直截了当的。首先,让我们解释一下区别:宏基本上在编译时评估,然后在运行时评估此评估的结果。有趣的是,编译时的求值将宏的字面值、未求值的参数作为数据获取,而不是像普通函数那样,在运行时获得求值的参数。因此,您的方法行不通,因为在 first-fn 收到它的参数(在运行时)时,它们已经被评估——在您的示例中,first-fn 接收 nil 作为参数。参照。 documentation at clojure-doc 以获得更好的解释。

现在,使用宏解决您的请求需要宏解析它接收的参数(记住:在编译时,代码就是数据)——即在您的示例中,它需要解析构建的序列(fn [a b c] (println "something")) up 函数调用你交给它。除了fn 之外,您可能还想涵盖其他情况(例如 # 简写),这就是它在一般情况下使问题不直截了当的原因。

这个解析最终可以通过普通函数解析来处理,例如一个序列。所以,首先尝试解决一个不同的难题:构建一个函数parse-code-sequence,它接受一个序列(看起来像你要移交的函数)并返回args和expr——注意@前面的引号(') 987654331@.

 user> (parse-code-sequence '(fn [a b c] (println "something")))
 {args: [a b c],  expr: (println "something")}

对此的一些提示:在此处的示例中,它显示了最常用的情况,序列仅包含三个元素,您不需要第一个元素。但一般情况要复杂一些,参见。 official documentation on fn

最后一点:当你实现宏时,你需要考虑它解析成什么——只是添加打印语句很容易,但你是否也想正常评估参数(所以你的宏变成了调试辅助)还是您想做其他事情?

更新以反映您的用例 您的 MY-GET 宏没有按照您的想法执行。

    1234563 987654337@?您需要检索源,但这通常是不可能的(参见retrieving the source of a function definition 上的这个 SO 问题)。
  1. 在调用 compile-route 之前还缺少反引号:您希望在运行时而不是编译时调用 compile-route。目前,宏评估的结果是调用compile-route 的结果(在编译时)。看看macroexpand,它会显示宏扩展的结果。基本上,您希望宏将调用返回到compile-route

我没有看到任何简单的方法可以完成您所寻找的。路由定义的参数向量定义了需要移交的内容。即使您将其提取到函数定义中,compojure 仍然需要知道将什么交给该函数。

【讨论】:

    【解决方案2】:

    这是您可以执行的操作的示例。

    (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"]
    

    【讨论】:

    • 这似乎适用于fn 你认为可以用defn 做同样的事情吗? (dissec second-fn) 其中second-fn 声明如下:(defn second-fn [a b c] (...))
    • 当然,只要修改自(first fn-src) -> "defn"、(second fn-src) -> "second-fn" 等(还有一个标记存在,因为有一个函数名称 @987654332 @没有)
    • 我得到的问题是:Don't know how to create ISeq from: clojure.lang.Symbol。听起来我应该做更多的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多