Проблема с передачей вектора в качестве привязки к макросу for - PullRequest
9 голосов
/ 15 июля 2010

У меня есть произвольное количество списков, которые я хотел бы обработать с помощью макроса for. Я хочу создать функцию, которая передает вектор в качестве привязки, так как количество списков варьируется.

Если я жестко закодирую привязку, она будет работать так, как я ожидаю:

=> (def list1 '("pink" "green"))
=> (def list2 '("dog" "cat"))
=> (for [A list1 B list2] (str A "-" B))
("pink-dog" "pink-cat" "green-dog" "green-cat")

Когда я пытаюсь создать вектор отдельно и использовать его в качестве связующего, я сталкиваюсь с проблемами. Здесь я вручную создаю вектор привязок:

=> (def testvector (vec (list 'A list1 'B list2)))

это выглядит нормально:

=> testvector
[A ("pink" "green") B ("dog" "cat")]
=> (class testvector)
clojure.lang.PersistentVector

Однако,

=> (for testvector (str A "-" B))
#<CompilerException java.lang.IllegalArgumentException: for requires a vector for its binding (NO_SOURCE_FILE:36)>

Я не понимаю, почему testvector не считается вектором, когда используется в качестве привязки для. Схватившись за соломинку, я поместил testvector в квадратные скобки, что делает макрос for счастливым (он видит вектор), но теперь у меня есть вектор с одним элементом (то есть вектор внутри вектора), и это не работает, потому что привязка должна быть парами имени и коллекции.

=> (for [testvector] (str A "-" B))
#<CompilerException java.lang.IllegalArgumentException: for requires an even number of forms in binding vector (NO_SOURCE_FILE:37)>

Буду признателен за любые предложения о том, как динамически передавать вектор в качестве привязки к.

Ответы [ 4 ]

5 голосов
/ 15 июля 2010

Ключ в том, что для - это макрос. Во время макроразложения testvector является символом. Он будет вычислять вектор во время оценки, но это не вектор с точки зрения макроса для .

user=> (defmacro tst [v] (vector? v))
#'user/tst
user=> (tst testvector)
false
user=> (vector? testvector)
true
user=> (defmacro tst2 [v] `(vector? ~v))
#'user/tst2
user=> (tst2 testvector)
true

Если вы проверите источник для макроса для (в core.clj), вы увидите, что для использует вызов вектор без кавычек? , точно так же как tst в примере выше.

0 голосов
/ 09 февраля 2015

Вы можете попытаться форсировать оценку вектора связывания.Вместо того, чтобы пытаться определить макрос, который будет обернуть макрос for, оберните его в функцию, например,

(defn for-fn [bindings expr]
  (eval `(for ~bindings ~expr))) 

Тогда вы можете построить вектор привязки с несколькими дополнительными ограничениями, поскольку все s-выражениявнутри вектора привязки должны быть действительными и содержать глагол в качестве первого элемента.

(let [bindings '[a (list 1 2) b (list 3 4) c (range 10 12)
                 :when (> (+ a b c) 15)]
      expr '(str a "-" b "-" c)]
  (for-fn bindings expr)) 

И с вашим примером:

(def list1 '("pink" "green"))
(def list2 '("dog" "cat"))
(def testvector (vector 'A (cons 'list  list1) 'B (cons 'list list2)))

(for-fn testvector '(str A "-" B))
=> ("pink-dog" "pink-cat" "green-dog" "green-cat")

Примечание: поскольку for-fn является функцией, вынеобходимо заключить в кавычки выражение (str A "-" B), чтобы предотвратить раннюю оценку (до того, как A & B будут связаны).

0 голосов
/ 08 февраля 2015

Хотя это не решение вашей проблемы, следует отметить, что то, что вы делаете, может быть легче достигнуто с помощью карты, чем, например,

user=> (def list1 '("pink" "green"))
#'user/list1
user=> (def list2 '("dog" "cat"))
#'user/list2
user=> (map #(str %1 "-" %2) list1 list2)
("pink-dog" "green-cat")
user=> 

Еще одна полезная техника при обучении и экспериментах - использование ключевых слов, а не строк. Это может уменьшить объем ввода, т. Е. Нет необходимости помещать значения в кавычки, а иногда помогает легче выявлять ошибки. Вместо (def list1 '("pink" "green")) вы можете просто сделать (def list1' (: pink: green)). Даже лучше, чем использовать списки, попробуйте использовать векторы, и тогда вам не нужно заключать их в кавычки (сохраняя еще одно нажатие клавиши).

0 голосов
/ 07 февраля 2015

Вот метод последней инстанции. Будьте осторожны, где бы вы ни увидели read-string, то есть код для Here Be Dragons! (Из-за угроз безопасности и отсутствия согласованности во время компиляции гарантирует поведение вашего кода)

(def list1 '("pink" "green"))
(def list2 '("dog" "cat"))
(for [A list1 B list2] (str A "-" B))

(def testvector (vec (list 'A list1 'B list2)))

(def testvector-vec (vec (list 'A (vec list1) 'B (vec list2))))

(def for-string (str "(for " testvector-vec "(str A \"-\" B))"))

(eval (read-string for-string))
> ("pink-dog" "pink-cat" "green-dog" "green-cat")
...