Подтвердить что ты не робот

В clojure, как применить макрос к списку?

В clojure, apply нельзя применить к макросу. Например, (apply and [true false]) вызывает исключение. Я думал о следующем обходном пути:

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

На первый взгляд это выглядело очень хорошо:

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

4b9b3361

Ответ 1

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

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

(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 дает хороший пример того, как достичь аналогичной цели без каких-либо макросов, что, вероятно, более уместно в большинстве ситуаций.

Ответ 2

Нет.

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

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

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

Ответ 3

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

(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 [email protected] [email protected])
              nil
              [email protected](map #(list 'quote %) args)
              '~rest-args))))

Использование:

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

Квалификация:

  • Макрос не следует указывать, а промежуточные аргументы передаются без макроса. Однако аргумент "rest" оценивается и должен оцениваться в виде списка символов или форм, каждый из которых будет передан без макроса.
  • Это не будет работать с макросами, которые используют аргумент &env.

Ответ 4

Этот подход не работает, если список аргументов может иметь неограниченную длину, но если вам нужно только применить к спискам до длины n, вы можете сделать оболочку с n арностей:

user> (defmacro foo [& rest] `(println [email protected]))
#'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.