Как развернуть последовательность (var-args) в отдельные элементы - PullRequest
6 голосов
/ 13 января 2011

Я хочу отправить var-аргументы функции в макрос, все еще как var-args. Вот мой код:

(defmacro test-macro
 [& args]
 `(println (str "count=" ~(count args) "; args=" ~@args)))

(defn test-fn-calling-macro
 [& args]
 (test-macro args))

Вывод (test-macro "a" "b" "c") - это то, что я хочу: count=3; args=abc

Вывод (test-fn-calling-macro "a" "b" "c"): count=1; args=("a" "b" "c"), потому что аргументы отправляются макросу как один аргумент. Как я могу расширить эти аргументы в моей функции для вызова макроса с 3 аргументами?

Полагаю, мне просто не хватает простой основной функции, но я не могу ее найти. Спасибо


РЕДАКТИРОВАТЬ 2 - Мой «реальный» код, показанный в разделе РЕДАКТИРОВАНИЯ ниже, не является допустимой ситуацией для использования этой техники.

Как указал @Brian, макрос xml-to-cass можно заменить на такую ​​функцию:

(defn xml-to-cass
  [zipper table key attr & path]
  (doseq [v (apply zf/xml-> zipper path)] (cass/set-attr! table key attr v)))

РЕДАКТИРОВАТЬ - следующий раздел выходит за рамки моего первоначального вопроса, но любое понимание приветствуется

Приведенный выше код является просто самым простым, с которым я могу прийти, чтобы точно определить мою проблему. Мой реальный код касается clj-cassandra и zip-фильтра. Это также может показаться излишним, но это всего лишь игрушечный проект, и я одновременно пытаюсь выучить язык.

Я хочу проанализировать некоторые XML, найденные на mlb.com, и вставить значения, найденные в базу данных кассандры. Вот мой код и мысли о нем.

Шаг 1 - Функция, которая работает нормально, но содержит дублирование кода

(ns stats.importer
  (:require
    [clojure.xml :as xml]
    [clojure.zip :as zip]
    [clojure.contrib.zip-filter.xml :as zf]
    [cassandra.client :as cass]))

(def root-url "http://gd2.mlb.com/components/game/mlb/year_2010/month_05/day_01/")

(def games-table (cass/mk-cf-spec "localhost" 9160 "mlb-stats" "games"))

(defn import-game-xml-1
  "Import the content of xml into cassandra"
  [game-dir]
  (let [url (str root-url game-dir "game.xml")
        zipper (zip/xml-zip (xml/parse url))
        game-id (.substring game-dir 4 (- (.length game-dir) 1))]
    (doseq [v (zf/xml-> zipper (zf/attr :type))] (cass/set-attr! games-table game-id :type v))
    (doseq [v (zf/xml-> zipper (zf/attr :local_game_time))] (cass/set-attr! games-table game-id :local_game_time v))
    (doseq [v (zf/xml-> zipper :team [(zf/attr= :type "home")] (zf/attr :name_full))] (cass/set-attr! games-table game-id :home_team v))))

Параметр import-game-xml-1 может быть, например, "gid_2010_05_01_colmlb_sfnmlb_1/". Я удаляю "gid_" и завершающий слеш, чтобы сделать его ключом игр ColumnFamily в моей базе данных.

Я обнаружил, что 3 doseq было много дублирования (и в финальной версии их должно быть больше 3). Таким образом, шаблонирование кода с использованием макроса показалось здесь уместным (поправьте меня, если я ошибаюсь).

Шаг 2 - Представление макроса для шаблонов кода (все еще работает)

(defmacro xml-to-cass
  [zipper table key attr & path]
  `(doseq [v# (zf/xml-> ~zipper ~@path)] (cass/set-attr! ~table ~key ~attr v#)))

(defn import-game-xml-2
  "Import the content of xml into cassandra"
  [game-dir]
  (let [url (str root-url game-dir "game.xml")
        zipper (zip/xml-zip (xml/parse url))
        game-id (.substring game-dir 4 (- (.length game-dir) 1))]
    (xml-to-cass zipper games-table game-id :type (zf/attr :type))
    (xml-to-cass zipper games-table game-id :local_game_time (zf/attr :local_game_time))
    (xml-to-cass zipper games-table game-id :home_team :team [(zf/attr= :type "home")] (zf/attr :name_full))))

Я считаю, что это улучшение, но я все еще вижу некоторое дублирование в том, чтобы всегда повторно использовать одни и те же 3 параметра в моих вызовах к xml-to-cass. Для этого я ввел промежуточную функцию.

Шаг 3 - Добавление функции для вызова макроса (проблема здесь)

(defn import-game-xml-3
  "Import the content of xml into cassandra"
  [game-dir]
  (let [url (str root-url game-dir "game.xml")
        zipper (zip/xml-zip (xml/parse url))
        game-id (.substring game-dir 4 (- (.length game-dir) 1))
        save-game-attr (fn[key path] (xml-to-cass zipper games-table game-id key path))]
    (save-game-attr :type (zf/attr :type)) ; works well because path has only one element
    (save-game-attr :local_game_time (zf/attr :local_game_time))
    (save-game-attr :home :team [(zf/attr= :type "home"] (zf/attr :name_full))))) ; FIXME this final line doesn't work

Ответы [ 4 ]

4 голосов
/ 13 января 2011

Вот простой код, который может светиться.

Макросы о генерации кода. Если по какой-то причине вы хотите, чтобы это происходило во время выполнения, вы должны построить и оценить код во время выполнения. Это может быть мощной техникой.

(defmacro test-macro
 [& args]
 `(println (str "count=" ~(count args) "; args=" ~@args)))

(defn test-fn-calling-macro
 [& args]
 (test-macro args))

(defn test-fn-expanding-macro-at-runtime
  [& args]
  (eval (cons `test-macro args)))

(defmacro test-macro-expanding-macro-at-compile-time
  [& args]
  (cons `test-macro args))

;; using the splicing notation

(defmacro test-macro-expanding-macro-at-compile-time-2
  [& args]
  `(test-macro ~@args))

(defn test-fn-expanding-macro-at-runtime-2
  [& args]
  (eval `(test-macro ~@args)))



(test-macro "a" "b" "c") ;; count=3; args=abc nil
(test-fn-calling-macro "a" "b" "c") ;; count=1; args=("a" "b" "c") nil

(test-fn-expanding-macro-at-runtime "a" "b" "c") ; count=3; args=abc nil
(test-macro-expanding-macro-at-compile-time "a" "b" "c") ; count=3; args=abc nil
(test-macro-expanding-macro-at-compile-time-2 "a" "b" "c") ; count=3; args=abc nil
(test-fn-expanding-macro-at-runtime "a" "b" "c") ; count=3; args=abc nil

Если созерцание вышесказанного не может быть поучительным, могу ли я предложить пару своих статей в блоге?

В этом я прохожу макросы с нуля, и как работает clojure, в частности:

http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html

И в этом я покажу, почему генерация кода во время выполнения может быть полезна:

http://www.learningclojure.com/2010/09/clojure-faster-than-machine-code.html

2 голосов
/ 13 января 2011

Макросы не волшебны.Они представляют собой механизм для преобразования кода во время компиляции в эквивалентный код;они не используются во время выполнения.Боль, которую вы чувствуете, состоит в том, что вы пытаетесь сделать что-то, что не должны пытаться делать.

Я не знаю, о какой библиотеке идет речь, но если cass/set-attr! - функция, я не вижу причинпочему определенный вами макрос должен быть макросом;это может быть функция вместо этого.Вы можете делать то, что хотите, если вместо этого вы можете переписать свой макрос как функцию.

2 голосов
/ 13 января 2011

Типичным способом использования коллекции в качестве отдельных аргументов функции является использование (apply function my-list-o-args)

(defn test-not-a-macro [& args]
    (print args))

(defn calls-the-not-a-macro [& args]
   (apply test-not-a-macro args))

, хотя вы не сможете использовать apply, потому что test-macro - это макрос.Чтобы решить эту проблему, вам понадобится обернуть тестовый макрос в вызов функции, чтобы вы могли применить его к нему.

(defmacro test-macro [& args]
    `(println ~@args))

(defn calls-test-macro [& args]
   (eval (concat '(test-macro) (args)))) ;you almost never need eval.

(defn calls-calls-test-macro [& args]
   (calls-test-macro args))

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

1 голос
/ 13 января 2011

Ваши требования не ясны.Я не понимаю, почему здесь необходим макрос для test-macro, если только вы не пытаетесь распечатать неоцененные формы , предоставленные вашему макросу.

Эти функции обеспечивают ожидаемые результаты,но это потому, что ваши данные выборки самооценки .

(defn test-args
  [& args]
  (println (format "count=%d; args=%s"
                    (count args)
                    (apply str args))))

или

(defn test-args
  [& args]
  (print (format "count=%d; args=" (count args)))
  (doseq [a args]
    (pr a))
  (newline))

Вы можете представить другие варианты, чтобы получить тот же результат.

Попробуйте вызвать эту функцию с чем-то, что само по себе не оценивается, и запишите результат:

(test-args (+ 1 2) (+ 3 4))

Вы хотели увидеть аргументы, напечатанные как "37" или "(+ 1 2) (+ 3 4) "?

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

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