В clojure, как применить макрос к списку? - PullRequest
26 голосов
/ 14 февраля 2012

В 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?

Ответы [ 5 ]

30 голосов
/ 14 февраля 2012

Вы не.

Макросы раскрываются во время оценки / компиляции, а не во время выполнения, поэтому единственная информация, которую они могут использовать, - это передаваемые аргументы , но не то, что оценивают аргументы во время выполнения . Вот почему буквальный вектор работает, потому что этот буквальный вектор существует во время компиляции, но a это просто символ; он будет вычисляться только для вектора во время выполнения.

Чтобы иметь and -подобное поведение для списков, используйте (every? identity coll).

Чтобы иметь or -подобное поведение для списков, используйте (some identity coll).

28 голосов
/ 14 февраля 2012

Проблема в том, что a это просто символ во время компиляции. Поэтому у макроса во время компиляции нет возможности увидеть, что он содержит, и выполнить необходимое расширение. В результате вам нужно развернуть макрос во время выполнения, используя eval .

Один из способов сделать это - просто обернуть макрос в функцию, которая вызывает eval, что можно сделать с помощью этого удобного макроса «functionize»:

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

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

Если хотите, вы также можете определить apply-macro в терминах functionize:

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

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

Сказав все это, я все же думаю, что лучше всего избегать макросов полностью, когда они на самом деле не нужны: они добавляют дополнительную сложность и лучше всего зарезервированы для случаев, когда вам действительно нужна генерация кода времени компиляции. В этом случае вы не: Ответ Алекса Таггарта дает хороший пример того, как достичь аналогичной цели без каких-либо макросов, что, вероятно, более подходит в большинстве ситуаций.

1 голос
/ 31 июля 2016

Конечно, правильный ответ - , не делайте этого . Но, поскольку я не могу устоять перед хорошим взломом:

(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.
0 голосов
/ 06 марта 2018

При применении аргументов к условным макросам, таким как, например, or, case, cond & condp.Вы можете использовать функции some, every & partition.Одиночное условие 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
0 голосов
/ 24 сентября 2017

Этот подход не работает, если список аргументов может иметь неограниченную длину , но если вам нужно применить только к спискам длиной до 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...