3

我正在尝试确定宏中的给定参数是否是函数,例如

(defmacro call-special? [a b]
  (if (ifn? a) 
    `(~a ~b)
    `(-> ~b ~a)))

这样以下两个调用都会生成“Hello World”

(call-special #(println % " World") "Hello")
(call-special (println " World") "Hello") 

但是,我不知道如何将“a”转换为 ifn? 能理解。任何帮助表示赞赏。

4

3 回答 3

4

您可能想问自己为什么要以call-special?这种方式定义。它似乎不是特别有用,甚至不会为您节省任何输入 - 您真的需要一个宏来执行此操作吗?

话虽如此,如果您决心让它工作,那么一种选择是查看内部a,看看它是否是一个函数定义:

(defmacro call-special? [a b]
  (if (#{'fn 'fn*} (first a)) 
    `(~a ~b)
    `(-> ~b ~a)))

这是有效的,因为#()函数文字被扩展为如下形式:

(macroexpand `#(println % " World"))
=> (fn* [p1__2609__2610__auto__] 
     (clojure.core/println p1__2609__2610__auto__ " World"))

我仍然认为一旦你开始做更复杂的事情(例如使用嵌套宏来生成你的函数),这个解决方案相当丑陋并且容易失败

于 2012-10-08T04:43:33.750 回答
2

首先,有几点:

  1. 宏只是接收输入[文字、符号或文字和符号的集合]和输出[文字、符号或文字和符号的集合]的函数。参数永远不是函数,因此您永远无法直接检查符号映射到的函数。
  2. (call-special #(println % " World") "Hello")包含阅读器宏代码。由于阅读器宏在常规宏之前执行,因此您应该在进行更多分析之前对其进行扩展。通过应用(read-string "(call-special #(println % \" World\") \"Hello\")")which become来做到这一点(call-special (fn* [p1__417#] (println p1__417# "world")) "Hello")

虽然一般来说,当您可能应该使用替代方法时,您何时想要使用某些东西并不明显,这就是我将如何处理它。

你需要打电话macroexpand-alla如果代码最终变成了一个(fn*)表单,那么它就保证是一个函数。然后你可以安全地发出 (~a ~b)。如果它宏扩展为最终成为一个符号,您也可以发出(~a ~b). 如果符号不是函数,则在运行时会抛出错误。最后,如果它宏扩展为一个列表(函数调用或特殊形式调用),例如(println ...),那么您可以发出使用线程宏的代码->

您还可以涵盖诸如表单宏扩展为数据结构但您没有指定所需行为的情况。

于 2012-10-08T05:04:05.313 回答
1

a在您的宏中只是一个 clojure 列表数据结构(它还不是一个函数)。所以基本上你需要检查数据结构a在评估时是否会产生一个函数,这可以如下所示完成:

(defmacro call-special? [a b]
  (if (or (= (first a) 'fn) (= (first a) 'fn*)) 
     `(~a ~b)
     `(-> ~b ~a)))

通过检查第一个元素a是符号fn*还是fn 用于创建函数。

此宏仅适用于 2 种情况:您将其传递给匿名函数或表达式。

于 2012-10-08T04:44:48.210 回答