【问题标题】:In clojure, how to apply a macro to a list?在clojure中,如何将宏应用于列表?
【发布时间】:2012-02-14 07:43:58
【问题描述】:

clojure 中,apply 不能应用于宏。例如(apply and [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

多么令人失望!!!!

知道如何修复apply-macro吗?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    你没有。

    宏在评估/编译期间扩展,而不是在运行时扩展,因此它们可以使用的唯一信息是传入的 args,但不是 args 在运行时评估的内容。这就是文字向量起作用的原因,因为该文字向量在编译时就在那里,但 a 只是一个符号;它只会在运行时评估为向量。

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

    要使列表具有类似or 的行为,请使用(some identity coll)

    【讨论】:

      【解决方案2】:

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

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

      (defmacro functionize [macro]
        `(fn [& args#] (eval (cons '~macro args#))))
      
      (let [a [true]] (apply (functionize and) a))
      => true
      

      如果你喜欢,你也可以根据 functionize 来定义 apply-macro:

      (defmacro apply-macro [macro args]
         `(apply (functionize ~macro) ~args))
      
      (let [a [true false]] (apply-macro and a))
      => false
      

      说了这么多,我仍然认为最好的办法是在真正不需要宏时完全避免使用宏:它们会增加额外的复杂性,最好留给真正需要编译时代码生成的情况。在这种情况下你不需要:Alex Taggart's answer 给出了一个很好的例子,说明如何在没有任何宏的情况下实现类似的目标,这在大多数情况下可能更合适。

      【讨论】:

      • @YehonathanSharvit:关键是(apply-macro and a)读取时扩展为(and <splice a into the form>),但a 仅在评估时定义> 时间。所以aunquote-splicing(即~@a)失败了,因为a还没有被定义。
      • 当然,a 是运行时的向量。但是如果你想在编译时用a 的内容构造一个(and ....) 表单,那是没有用的,因为编译器看不到a 的内容。因此,一旦您知道a 包含的内容,就需要在运行时再次 调用编译器,因此需要进行eval。这有点令人费解,但希望逻辑有意义.....
      • 我刚刚发明了functionize来回答你的问题。我认为它是相当通用的,但还没有在现场完全测试过:-)
      • 作为一个社区,我们应该尽量避免鼓励没有经验的人过度使用宏,尤其是eval,尤其是当有更好的工具来完成这项工作时。
      • Alex - 完全同意,我添加了一个合适的警告:-)
      【解决方案3】:

      将 args 应用于条件宏时,例如 orcasecondcondp。您可以使用someeverypartition 函数。这些示例中省略了单个 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? 可用于and

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

      【讨论】:

        【解决方案4】:

        当然,正确的答案是不要这样做。但是,因为我无法抗拒一个好的 hack:

        (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)
        

        资格:

        1. 宏不应被引用,并且中间的参数未经计算地传递给宏。但是,“rest”参数被评估,并且必须评估为符号或形式的列表,每个符号或形式都将不经评估地传递给宏。
        2. 这不适用于使用 &env 参数的宏。

        【讨论】:

          【解决方案5】:

          这种方法如果参数列表可以有无限长度则不起作用,但如果您只需要应用到长度不超过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)
          

          在我的情况下,这在没有 eval 的情况下完成了我所需要的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-19
            • 1970-01-01
            • 2014-05-06
            • 1970-01-01
            • 2014-06-01
            相关资源
            最近更新 更多