如何将序列(var-args)扩展为不同的项目

时间:2011-01-13 00:55:34

标签: clojure

我想将函数的var-args发送到宏,仍然作为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")因为args作为单个参数发送给宏。如何在我的函数中扩展这个args以便使用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-filter。它也可能看起来过度工程,但它只是一个玩具项目,我正在尝试同时学习这门语言。

我想解析mlb.com上的一些XML,并将找到的值插入到cassandra数据库中。这是我的代码及其背后的想法。

第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))))

我认为这是一种改进,但我仍然看到在我对xml-to-cass的调用中总是重复使用相同的3个参数时会出现一些重复。那就是我引入了一个中间函数来处理那些。

第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 个答案:

答案 0 :(得分:4)

这是一些可能很有启发性的简单代码。

宏是关于代码生成的。如果您希望在运行时发生这种情况,出于某种原因,那么您必须在运行时构建和评估代码。这可能是一种强大的技术。

(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

答案 1 :(得分:2)

将集合用作函数的单个参数的典型方法是使用(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))

这实际上是宏很难编写的一个很好的例子。 (有些人会说他们不能干净利落地组成,虽然我认为这是一种夸张)

答案 2 :(得分:2)

宏并不神奇。它们是一种在编译时将代码转换为等效代码的机制;它们不会在运行时使用。你感受到的痛苦是因为你正在尝试做一些你不应该做的事情。

我不知道有问题的库,但如果cass/set-attr!是一个函数,我认为没有理由为什么你定义的宏必须是一个宏;它可能是一个功能。如果您可以将宏重写为函数,则可以执行您想要执行的操作。

答案 3 :(得分:1)

您的要求不明确。我不明白为什么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)”?

如果您反而尝试了解宏及其扩展,而不是解决此特定问题,请调整您的问题以进一步探讨。