在clojure中,如何将宏应用于列表?

在clojure中,如何将宏应用于列表?,clojure,Clojure,在clojure中,apply不能应用于宏。例如,(apply和[true-false])引发异常。我正在考虑以下解决方法: (defmacro apply-macro[func args] `(~func ~@args)) 乍一看,它似乎运行得很好: (apply-macro and [true 5]); 5 (apply-macro and [true 5 0]); 0 (let [a 0] (apply-macro and [true a])); 0 但是,当我向它传递一个指向向量的变

clojure
中,
apply
不能应用于宏。例如,
(apply和[true-false])
引发异常。我正在考虑以下解决方法:

(defmacro apply-macro[func args] `(~func ~@args))
乍一看,它似乎运行得很好:

(apply-macro and [true 5]); 5
(apply-macro and [true 5 0]); 0
(let [a 0] (apply-macro and [true a])); 0
但是,当我向它传递一个指向向量的变量时,它崩溃了

(let [a [true]] (apply-macro and a));  java.lang.IllegalArgumentException:
   ;Don't know how to create ISeq from: clojure.lang.Symbol
真令人失望


你知道如何修复
应用宏吗?

问题是
a
在编译时只是一个符号。因此,编译时宏无法查看它包含的内容并进行必要的扩展。因此,您需要在运行时使用展开宏

一种方法是将宏包装在调用eval的函数中,这可以通过这个方便的“functionize”宏完成:

如果愿意,还可以根据functionize定义apply宏:

(defmacro apply-macro [macro args]
   `(apply (functionize ~macro) ~args))

(let [a [true false]] (apply-macro and a))
=> false
尽管如此,我仍然认为最好的办法是在不需要宏的时候完全避免它们:它们增加了额外的复杂性,并且最好是保留在真正需要编译时代码生成的情况下。在这种情况下,您没有:提供了一个很好的示例,说明如何在没有任何宏的情况下实现类似的目标,这在大多数情况下可能更合适。

您没有

宏在计算/编译时展开,而不是在运行时展开,因此它们可以使用的唯一信息是传入的arg,而不是arg在运行时计算的内容。这就是文字向量工作的原因,因为文字向量在编译时就在那里,但是
a
只是一个符号;它将在运行时仅计算为向量

要使列表具有类似于
的行为,请使用
(every?identity coll)


要使列表具有类似于
的行为,请使用
(一些身份标签)
当然,正确的答案是不要这样做。但是,既然我无法抗拒一个好的黑客:

(defmacro apply-macro
  "Applies macro to the argument list formed by prepending intervening
  arguments to args."
  {:arglists '([macro args]
               [macro x args]
               [macro x y args]
               [macro x y z args]
               [macro a b c d & args])}
  [macro & args+rest]
  (let [args (butlast args+rest)
        rest-args (eval (last args+rest))]
    `(eval
       (apply (deref (var ~macro))
              '(~macro ~@args ~@rest-args)
              nil
              ~@(map #(list 'quote %) args)
              '~rest-args))))
用法:

hackery> (->> (range 5) rest rest rest rest)
(4)
hackery> (apply-macro ->> (range 5) (repeat 4 'rest))
(4)
资格:

  • 该宏不应被引用,并且中间的参数会被传递给该宏而不进行计算。但是,“rest”参数是经过计算的,并且必须计算为符号或形式列表,每个符号或形式都将未经计算传递给宏
  • 这不适用于使用参数的宏

  • 如果参数列表可以具有无界长度,则此方法不起作用,但如果您只需要应用于长度小于等于
    n
    的列表,则可以使用
    n
    算术生成包装函数:

    user> (defmacro foo [& rest] `(println ~@rest))
    #'user/foo
    user> (apply foo [1 2])
    CompilerException java.lang.RuntimeException: Can't take value of a macro: #'user/foo, compiling:(*cider-repl repo*:865:7) 
    user> (defn foo-up-to-ten-args
      ([a]                   (foo a))
      ([a b]                 (foo a b))
      ([a b c]               (foo a b c))
      ([a b c d]             (foo a b c d))
      ([a b c d e]           (foo a b c d e))
      ([a b c d e f]         (foo a b c d e f))
      ([a b c d e f g]       (foo a b c d e f g))
      ([a b c d e f g h]     (foo a b c d e f g h))
      ([a b c d e f g h i]   (foo a b c d e f g h i))
      ([a b c d e f g h i j] (foo a b c d e f g h i j)))
    #'user/foo-up-to-ten-args
    user> (apply foo-up-to-ten-args [1 2])
    1 2
    nil
    user> (apply foo-up-to-ten-args (range 0 10))
    0 1 2 3 4 5 6 7 8 9
    nil
    user> (apply foo-up-to-ten-args (range 0 11))
    ArityException Wrong number of args (11) passed to: user/foo-up-to-ten-args  clojure.lang.AFn.throwArity (AFn.java:429)
    

    在我的例子中,当将args应用于条件宏(例如
    case
    cond
    &
    condp
    )时,这就满足了我的需要,而无需
    eval

    。您可以使用
    some
    every
    分区
    功能。这些示例中省略了单个else子句,但可以很容易地添加

    ;apply to the 'or' macro
    (some identity [nil false 1 2 3])
    => 1
    
    ;apply to the 'case' macro.
    (some
      (fn [[case value]]
        (and (= case 2) value))
      (partition 2 [1 "one" 2 "two" 3 "three"]))
    => "two"
    
    ;apply to the 'cond' macro
    (some
      (fn [[case value]]
        (and case value))
      (partition 2 [false "one" true "two" false "three" :else "four"]))
    => "two"
    
    ;apply to the 'condp' macro
    (let [[f v & args] [= 2 1 "one" 2 "two" 3 "three"]]
      (some
        (fn [[case value]]
          (and (f case v) value))
        (partition 2 args)))
    
    every?
    可用于

    ;apply to the 'and' macro
    (every? identity [true true true])
    => true
    

    @YehonathanSharvit:关键是
    (应用宏和a)
    在读取时被扩展为
    (和)
    ,但
    a
    仅在计算时被定义。因此
    a
    (即
    ~@a
    )的
    unquote拼接
    失败,因为
    a
    尚未定义。当然,
    a
    是运行时的向量。但是,如果您想在编译时用
    a
    的内容在构建
    (和…)
    表单,这是没有用的,因为编译器看不到
    a
    的内容。因此,一旦知道
    a
    包含的内容,就需要在运行时再次调用编译器,因此需要eval。这有点复杂,但希望逻辑有意义……我刚刚发明了functionize来回答您的问题。我认为这是一个相当通用的工具,但还没有在现场进行测试:-)作为一个社区,我们应该尽量避免鼓励没有经验的人过度使用宏,尤其是
    eval
    ,特别是当有更好的工具用于此项工作时。Alex-完全同意,我已经添加了一个适当的警告:-)
    ;apply to the 'and' macro
    (every? identity [true true true])
    => true