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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在clojure中,如何将宏应用于列表?相关的知识,希望对你有一定的参考价值。

clojureapply不能应用于宏观。例如,(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吗?

答案

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

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

(defmacro functionize [macro]
  `(fn [& args#] (eval (cons '~macro args#))))

(let [a [true]] (apply (functionize and) a))
=> true

如果您愿意,还可以根据函数化定义apply-macro:

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

(let [a [true false]] (apply-macro and a))
=> false

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

另一答案

你没有。

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

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

要使列表具有类似or的行为,请使用(some 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)

资格:

  1. 宏不应该被引用,并且干预的论点被无价值地传递给宏。但是,“rest”参数将被评估,并且必须评估为符号或表单列表,每个符号或表单都将被未评估地传递给宏。
  2. 这不适用于使用&env参数的宏。
另一答案

如果参数列表可以具有无限长度,则此方法不起作用,但如果您只需要应用长度为n的列表,则可以使用n arities创建包装函数:

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,这就是我所需要的。

另一答案

将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

以上是关于在clojure中,如何将宏应用于列表?的主要内容,如果未能解决你的问题,请参考以下文章

如何在片段中填充列表视图?

如何从片段内的列表视图打开链接网址?

由于未找到 ID 错误的视图,列表片段未出现

Clojure - 宏中的 let 不起作用

如何在片段中使用 GetJsonFromUrlTask​​.java

将宏作为 CString 引用传递