2011-01-13 20 views
6

我想將函數的var-args發送到宏,仍然是var-args。 這裏是我的代碼:如何將序列(var-args)擴展爲不同的項目

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

(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被作爲一個參數傳遞給宏。如何在我的函數中擴展這個參數以便用3個參數調用宏?

我想我只是缺少一個簡單的核心功能,但我無法找到它。由於


EDIT 2 - 我的「真實」的代碼,在下面EDIT部分顯示的是不使用這一技術的有效情況。

正如@布賴恩指出,宏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 [email protected])] (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

下面是一些簡單的代碼,可能是照亮。

宏是關於代碼生成的。如果您希望在運行時發生這種情況,出於某種原因,您必須在運行時構建和評估代碼。這可能是一個強大的技術。

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

(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 [email protected])) 

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



(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

使用集合作爲單獨的參數的函數的典型方法是使用(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 [email protected])) 

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

這實際上是一種方法的宏都難以構成一個的一個很好的例子。 (有人會說他們不能很乾淨地組成,儘管我認爲這是一種暴動)

+0

ps:我不在我的REPL,所以如果這是壞的請編輯(或評論,我會修復,當我回家) – 2011-01-13 01:25:27

+0

謝謝。我試過這段代碼,但是我得到一個`ClassCastException:clojure.lang.ArraySeq不能轉換爲clojure.lang.IFn`。任何想法爲什麼?我確實會對我的問題有一個直接的答案,但看起來非常複雜。我編輯了我的問題以提供關於我的「真實」代碼的所有細節。也許它可以幫助。非常感謝。 – Damien 2011-01-13 04:11:24

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

是你想看看(1 + 2)打印爲「37」或」參數( + 3 4)「?

如果您想要了解一般的宏及其擴展,而不是解決這個特定的問題,請調整您的問題以進一步探究。

+0

感謝您的回答。我的println代碼完全是人爲的,並且儘可能簡單以查明問題。我不想用不相關的代碼打擾人們。但是既然你問了,我編輯了我的問題,並提供了爲什麼我認爲我需要一個宏的所有細節。我不能更具體; o) – Damien 2011-01-13 04:07:35

2

宏不是魔術。它們是在編譯時將代碼轉換爲等效代碼的機制;它們不在運行時使用。你感到的痛苦是因爲你正在嘗試做一些你不應該做的事情。

我不知道庫中的問題,但如果cass/set-attr!是一個函數,我看不出爲什麼你定義的宏必須是宏;它可能是一個功能。你可以做你想做的事,如果你可以改寫你的宏作爲一個函數。

+0

非常感謝@Brian。你是完全正確的。我能夠使它作爲一個功能工作。看來我還沒有獲得足夠的技能來玩宏。但我對接受你的回答猶豫不決。難道沒有理論上的情況下,有人會真的需要「轉發」從功能到宏的變量嗎? @亞瑟的「eval」函數的答案可能更適合那些人。 – Damien 2011-01-13 05:19:01

相關問題