En clojure, comment appliquer une macro à une liste?

Dans clojure, apply ne peut pas être appliqué à une macro. Par exemple, (apply and [true false]) déclenche une exception. Je pensais à la solution de contournement suivante:

(defmacro apply-macro[func args] `(~func ~@args))

À première vue, cela semblait plutôt bien fonctionner:

(apply-macro and [true 5]); 5
(apply-macro and [true 5 0]); 0
(let [a 0] (apply-macro and [true a])); 0

Mais, quand je lui ai passé une variable qui pointe vers un vecteur, elle s'est effondrée.

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

Quelle déception!!!!

Une idée de comment réparer apply-macro?

25
demandé sur viebel 2012-02-14 11:43:58

5 réponses

Le problème est que a est juste un symbole au moment de la compilation. Il n'y a donc aucun moyen pour une macro à la compilation de voir ce qu'elle contient et de faire l'expansion nécessaire. Par conséquent, vous devez développer la macro au moment de l'exécution en utilisant eval.

Une façon de le faire est simplement d'envelopper la macro dans une fonction qui appelle eval, ce qui peut être fait avec cette macro pratique "fonctionnaliser":

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

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

Si vous le souhaitez, vous pouvez également définir apply-macro en termes de fonctionnaliser:

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

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

Ayant dit tout cela, je pense toujours que la meilleure chose à faire est d'éviter complètement les macros quand elles ne sont pas vraiment nécessaires: elles ajoutent une complexité supplémentaire et sont mieux réservées aux cas où vous avez vraiment besoin de génération de code de compilation. Dans ce cas, vous ne le faites pas: la réponse D'Alex Taggart donne un bon exemple de la façon d'atteindre un objectif similaire sans macros, ce qui est probablement plus approprié dans la plupart des situations.

25
répondu mikera 2017-05-23 12:02:18

Vous ne le faites pas.

Les Macros sont développées pendant l'évaluation / la compilation, pas au moment de l'exécution, donc les seules informations qu'elles peuvent utiliser sont les args transmis, mais pas ce que les args évaluent à l'exécution. C'est pourquoi un vecteur littéral fonctionne, car ce vecteur littéral est là au moment de la compilation, mais a n'est qu'un symbole; il n'évaluera un vecteur qu'au moment de l'exécution.

Pour avoir un comportement similaire à and pour les listes, Utilisez (every? identity coll).

Pour avoir un comportement similaire à or pour les listes, Utilisez (some identity coll).

28
répondu Alex Taggart 2014-03-12 15:46:52

Bien sûr, la bonne réponse est Ne faites pas cela . Mais, puisque je ne peux pas résister à un bon 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))))

Utilisation:

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

Qualifications:

  1. la macro ne doit pas être citée, et les arguments intermédiaires sont passés sans évaluation à la macro. Cependant, l'argument" rest " est évalué, et doit évaluer à une liste de symboles ou de formes, dont chacun sera passé sans évaluation à la macro.
  2. cela ne fonctionnera pas avec les macros qui utilisent l'&env argument.
1
répondu Radon Rosborough 2016-07-31 14:51:38

Cette approche ne fonctionne pas si la liste d'arguments peut avoir une longueur illimitée , mais si vous devez seulement appliquer aux listes jusqu'à la longueur n vous pouvez créer une fonction wrapper avec 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)

Dans mon cas, cela a fait ce dont j'avais besoin sans eval.

0
répondu Gordon Gustafson 2017-09-23 21:06:08

Lors de l'application d'arguments pour une macros conditionnelles telles que or, case, cond & condp par exemple. Vous pouvez utiliser l'some, every & partition fonctions. La clause else unique est laissée de côté dans ces exemples mais peut être ajoutée assez facilement

;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? peut être utilisé pour la and macro

;apply to the 'and' macro
(every? identity [true true true])
=> true
0
répondu Caleb Macdonald Black 2018-03-06 04:38:43