2011-01-13 11 views
6

Ich möchte var-args einer Funktion an ein Makro senden, immer noch als var-args. Hier ist mein Code:So erweitern Sie eine Sequenz (var-args) in verschiedene Elemente

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

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

Der Ausgang des (test-macro "a" "b" "c") ist das, was ich will: count=3; args=abc

Die Ausgabe von (test-fn-calling-macro "a" "b" "c") ist: count=1; args=("a" "b" "c") da args als ein einziges Argument an das Makro gesendet wird. Wie kann ich diese Argumente in meiner Funktion erweitern, um das Makro mit den 3 Argumenten aufzurufen?

Ich denke, ich vermisse nur eine einfache Kernfunktion, aber ich bin nicht in der Lage, es zu finden. Dank


EDIT 2 - My "echter" Code unten in EDIT Abschnitt dargestellt ist keine gültige Situation, diese Technik zu verwenden.

Wie @ Brian wies darauf hin, das Makro xml-to-cass kann mit einer Funktion wie folgt ersetzt:

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

EDIT - der folgende Abschnitt geht über meine ursprüngliche Frage aber jede Einsicht ist willkommen

Der obige Code ist nur das einfachste, mit dem ich mein Problem identifizieren konnte. Mein realer Code behandelt clj-cassandra und zip-filter. Es mag auch übertrieben aussehen, aber es ist nur ein Spielzeugprojekt und ich versuche gleichzeitig, die Sprache zu lernen.

Ich möchte einige XML-Dateien auf mlb.com parsen und Werte in eine Cassandra-Datenbank einfügen. Hier ist mein Code und das Denken dahinter.

Schritt 1 - Funktion, der gut arbeitet, aber Code enthält Vervielfältigung

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

Der Parameter import-game-xml-1"gid_2010_05_01_colmlb_sfnmlb_1/" zum Beispiel sein kann. Ich entferne den "gid_" und den abschließenden Schrägstrich, um ihn zum Schlüssel der ColumnFamily-Spiele in meiner Datenbank zu machen.

Ich fand, dass die 3 doseq viel Duplizierung waren (und es sollte mehr als 3 in der endgültigen Version sein). So erschien hier ein Templat mit einem Makro als richtig (korrigiere mich, wenn ich falsch liege).

Schritt 2 - ein Makro für die Code-Templating Einführung (funktioniert noch)

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

Ich glaube, dass eine Verbesserung, aber ich immer noch einige Überschneidungen in immer Wiederverwendung der gleichen 3 Parameter in meine Anrufe zu xml-to-cass sehen. Das ist, wo ich eine Zwischenfunktion eingeführt habe, um auf jene aufzupassen.

Schritt 3 - eine Funktion Hinzufügen des Makro aufzurufen (das Problem ist hier)

(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 

Antwort

4

Hier ist ein einfacher Code, der beleuchtet werden kann.

Bei Makros geht es um die Codegenerierung. Wenn das zur Laufzeit aus irgendeinem Grund geschehen soll, müssen Sie den Code zur Laufzeit erstellen und auswerten. Dies kann eine leistungsfähige Technik sein.

Wenn sich die obige Betrachtung nicht als aufschlussreich erweist, könnte ich Ihnen ein paar meiner eigenen Blog-Artikel vorschlagen?

In diesem von Grund auf neu durch Makros gehen, und wie die Arbeit des clojure insbesondere:

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

Und in diesem einen zeigen, dass ich, warum die Laufzeitcodegenerierung könnte nützlich sein:

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

+0

Vielen Dank, dass Sie sich die Zeit genommen haben, das zu schreiben. Ich fühle mich jetzt irgendwie erleuchtet. Ich werde mit Sicherheit auch Ihre Blogposts durchgehen. Schade, dass ich Lisp nicht als Kind gelernt habe, sondern als sturer Erwachsener. Es fällt mir schwer, meinen Kopf herumzukriegen. Aber bis jetzt ist es den Schmerz wert. Ich werde nicht aufgeben und hoffentlich das Denken begreifen. – Damien

+0

Entschuldigung, in Kata zu antworten! Es gibt Feinheiten, und man muss eine Weile spielen, bevor man es versteht. Mach dir keine Sorgen um dein Gehirn. Ich habe erst nach ungefähr 35 Jahren ein Rezept berührt. Es dauert ungefähr ein Jahr. Eines Tages wirst du versuchen, etwas in einer Sprache mit Syntax zu schreiben, und plötzlich erkennst du, dass du dich darin gefangen fühlst. –

+1

Der Weg, um wirklich zu grok ist es, durch SICP zu gehen und alle Übungen zu machen. http://mitpress.mit.edu/sicp/ In der Tat erwähnt es kaum Makros, aber über die Zeit, wo Sie einen Schema-Interpreter in Schema schreiben, werden Sie genau verstehen, was im Evaluator vor sich geht, und an diesem Punkt sind Makros eine triviale Folge. Selbst wenn Ihr Ziel ist, in clojure oder einem anderen komplexen Lispeln zu programmieren, ist dies eine gute Sache zu tun, weil Schema so einfach ist, dass Sie die Ideen sehen können. Ein bisschen wie Java zu lernen, wenn Sie wirklich C++ wollen. –

2

Der typische Weg, um eine Sammlung als einzelne Argumente an eine Funktion zu verwenden, ist (apply function my-list-o-args) zu verwenden

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

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

obwohl Sie nicht anwenden können, weil Test-Makro ein Makro ist. Um dieses Problem zu lösen, müssen Sie das Testmakro in einen Funktionsaufruf einbinden, damit Sie es anwenden können.

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

Dies ist tatsächlich ein wirklich gutes Beispiel für eine der Möglichkeiten, wie Makros schwer zu komponieren sind. (Einige würden sagen, sie können nicht sauber zusammengesetzt werden, obwohl ich denke, das ist eine Übertreibung)

+0

ps: ich bin nicht bei meiner REPL, also wenn das kaputt ist, bitte bearbeiten (oder kommentieren und ich werde reparieren, wenn ich nach hause komme) –

+0

Danke. Ich habe diesen Code ausprobiert, aber ich bekomme eine 'ClassCastException: clojure.lang.ArraySeq kann nicht in clojure.lang.IFn' umgewandelt werden. Irgendeine Idee warum? Ich würde zwar eine direkte Antwort auf meine Frage finden, aber es sieht überraschend komplex aus. Ich habe meine Frage bearbeitet, um alle Details zu meinem "echten" Code zu erhalten. Vielleicht könnte es helfen. Danke vielmals. – Damien

1

Ihre Anforderungen sind nicht klar. Ich sehe nicht, warum ein Makro hier für test-macro notwendig ist, es sei denn, Sie versuchen, die nicht ausgewerteten Formulare zu drucken, die zu Ihrem Makro geliefert werden.

Diese Funktionen liefern Ihre erwarteten Ergebnisse, aber das liegt daran, dass Ihre Beispieldaten selbstbewertend waren.

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

oder

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

können Sie andere Varianten vorstellen, auf das gleiche Ergebnis zu erhalten.

versuchen, diese Funktion mit etwas aufrufen, die nicht an sich selbst nicht bewerten, und beachten Sie das Ergebnis:

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

Waren Sie die Argumente wie „37“ oder „(+ 1 2) gedruckt, um zu sehen suchen (+ 3 4) "?

Wenn Sie stattdessen im Allgemeinen versuchen, etwas über Makros und deren Erweiterung zu erfahren, anstatt dieses spezielle Problem zu lösen, stimmen Sie Ihre Frage ab, um weiter zu suchen.

+0

Danke für die Antwort. Mein Code mit dem println war völlig künstlich und so einfach wie möglich, um das Problem zu lokalisieren. Ich wollte die Leute nicht mit irrelevantem Code belästigen. Aber seit du gefragt hast, habe ich meine Frage bearbeitet und alle Details darüber angegeben, warum ich dachte, dass ich ein Makro brauche. Ich kann nicht genauer sein; o) – Damien

2

Makros sind keine Magie. Sie sind ein Mechanismus, um Code bei der Kompilierung in gleichwertigen Code zu konvertieren. Sie werden zur Laufzeit nicht verwendet. Der Schmerz, den du fühlst, ist, weil du versuchst, etwas zu tun, was du nicht versuchen solltest.

Ich weiß nicht die Bibliothek in Frage, aber wenn cass/set-attr! eine Funktion ist, sehe ich keinen Grund, warum das Makro, das Sie definierten, ein Makro sein muss; es könnte stattdessen eine Funktion sein. Sie können tun, was Sie tun möchten, wenn Sie Ihr Makro stattdessen als Funktion neu schreiben können.

+0

Vielen Dank @Brian. Du hast vollkommen recht. Ich konnte es als Funktion funktionieren lassen. Es scheint, dass ich noch nicht genug Fähigkeiten erworben habe, um mit Makros zu spielen. Aber ich zögere, deine Antwort zu akzeptieren. Könnte es nicht einen theoretischen Fall geben, in dem jemand Var-Args wirklich von Funktion zu Makro "weiterleiten" müsste? @ Arthurs Antwort mit der "eval" -Funktion kann für diese Leute passender sein. – Damien

Verwandte Themen