2013-05-23 6 views
6

Während ich das Konzept der Homoiconicity falsch interpretieren kann, habe ich es als "Code ist Daten" verstanden.Kann eine Clojure-Funktion zerlegt werden?

So kann ich Code wie folgt schreiben:

(def subject "world") 
(def helo '(str "Hello " subject)) 

An diesem Punkt ist helo nur Daten, sondern kann als Code wie folgt ausgeführt:

(eval helo) 

, die „Hallo Welt zurück ".

Ich kann auch zur Behandlung von helo als Daten weiter:

(first helo) 
(count helo) 

die jeweils str und 3 zurückgibt.

So weit so gut. Aber sobald ich den Code in einer Funktion wickeln, scheine ich die Fähigkeit zu verlieren, um Code als Daten zu behandeln:

(defn helofn [subject] 
    (str "Hello " subject)) 

Wie zersetzen ich helofn? Es scheint, dass ich es nicht als Daten behandeln kann; wenn ich dies tun:

(count helofn) 

Ich erhalte eine Ausnahme:

java.lang.UnsupportedOperationException: Zählen Sie nicht auf diese Art unterstützt: Benutzer $ helofn

Gibt es eine andere Art und Weise zu zerlegen helofn, oder erwarte ich nur zu viel von Homoiconicity?

Antwort

6

defn ist nur ein macro:

(macroexpand '(defn helofn [subject] 
    (str "Hello " subject))) 

(def helofn (clojure.core/fn ([subject] (str "Hello " subject)))) 

Wenn Sie helofn definieren die Art und Weise Sie helo definiert sind, können Sie es als Daten behandeln:

(def helofn '(fn [subject] 
    (str "Hello " subject))) 

Jetzt können Sie Evaluierungs- und rufen Sie diese Funktion:

((eval helofn) "world") 

und als Daten zu behandeln:

(count helofn) 

Aber, wenn Sie defn Makro Sie helofn Variable mit kompilierte Funktion verknüpft und nicht mit ihm Code.

Es ist nicht nur Funktionen. Angenommen, Sie hello mit dem folgenden Code definiert:

(def helo (str "Hello " subject)) 

Jetzt hello mit „Hallo Welt“ string zugeordnet ist und nicht mit (str "Hello " subject) Code. So, jetzt gibt es keine Möglichkeit, den Code zu erhalten, mit dem diese Zeichenfolge erstellt wurde.

N.B. Wenn Sie Clojure-Code als Daten behandeln möchten, sollten Sie sich dessen macros ansehen. Jeder Code, der an ein Makro übergeben wird, wird als Daten behandelt, und alle von einem Makro zurückgegebenen Daten werden als Code behandelt.

1

Es sieht aus wie keine basierend auf

get a clojure function's code

und

Can you get the "code as data" of a loaded function in Clojure?

Grundsätzlich können Sie die Quelle aus einer Funktion in einer .clj Datei definiert erhalten, aber es gibt keine zuverlässige Möglichkeit zu Abrufen der Datenstrukturen, die eine Funktion aus der Funktion allein erstellt haben.

EDIT: Auch ich denke, Sie erwarten zu viel von Homoiconicity. Der Code selbst ist Daten ja, aber es ist ziemlich Standard, nicht in der Lage zu sein, den ursprünglichen Quellcode basierend auf dem von diesem Code ausgegebenen Artefakt abzurufen. Wie wenn ich 2 habe, kann ich nicht wissen, dass es von (+ 1 1) oder (- 4 2) auf dieselbe Weise erzeugt wurde wie eine Funktion, die durch den Aufruf von fn über andere Datenstrukturen erzeugt wird, die interpretiert werden als Code.

9

Die helofn Definition ist Daten, aber du lässt es ausgewertet werden (so wie Sie die helo Liste explizit ausgewertet). Wenn Sie die Definition in der gleichen Weise wie helo behandelt, dann wird es Daten bleiben und zugänglich, was auch immer Transformationen wollen Sie anwenden:

(def helofndata '(defn helofn [subject] 
        (str "Hello " subject)) 

=> (second helofndata) 
helofn 
=> (eval helofndata) 
#'user/helofn 
3

Homoikonizität ein sehr leistungsfähiges Konzept ist, und ich glaube nicht, Sie erwarten zu viel davon.

defn ist eigentlich ein Makro, das die def spezielle Form verwendet eine Funktion zu definieren, so:

(defn sq [x] 
    (* x x)) 

tatsächlich gleichwertig ist:

(def sq (fn ([x] (* x x)))) 

So defn hier ist die args Empfang sq [x] (* x x), erstellt dann die Liste (def sq (fn ([x] (* x x)))), gibt sie als Ergebnis des Makros zurück und wird dann ausgewertet. Dies alles geschieht durch die Manipulation von Listen, Karten, Vektoren, Symbolen usw. durch das Makro defn.

Die Tatsache, dass in Clojure Sie nicht die ursprüngliche Liste der Symbole erhalten können, aus denen Sie eine Funktion definiert haben, hat damit zu tun, dass in Clojure der gesamte Code kompiliert wird. Dies ist der Grund, warum die Bewertung (fn [x] 1) in der REPL so etwas wie #<user$eval809$fn__810 [email protected]> zurückgibt. Aber immer noch, wie in einer previous answer erwähnt, ist der Code, der ausgewertet wird, Daten.

Vielleicht gehe ich damit zu weit, aber wenn Sie für jede Funktion, die Sie definieren möchten, die Daten haben, aus denen sie erstellt wurde, können Sie sie zu ihren Metadaten hinzufügen, indem Sie ein eigenes benutzerdefiniertes Makro erstellen.

Hier ist eine naive Implementierung für einen solchen Makro:

(defmacro defn* [x & body ] 
    (let [form `'~&form 
     x (vary-meta x assoc :form form)] 
    `(defn ~x [email protected]))) 
;=> #'user/defn* 

(defn* sq [x] 
    (* x x)) 
;=> #'user/sq 

(:form (meta #'sq)) 
;=> (defn* sq [x] (* x x)) 

&form ist ein implizites Argument (zusammen mit & env), die das gesamte (unevaluierten) Form enthält, mit dem das Makro aufgerufen wurde (dh die Daten, die wird vom Compiler ausgewertet).

Hoffe das hilft und es bringt nicht mehr Verwirrung.

Verwandte Themen