Laut diesem Artikel gibt die folgende Zeile des Lisp-Codes "Hello world" in der Standardausgabe aus.
(format t "hello, world")
Lisp, eine homoikonische Sprache , kann Code wie folgt als Daten behandeln:
Stellen Sie sich nun vor, wir hätten folgendes Makro geschrieben:
(defmacro backwards (expr) (reverse expr))
rückwärts ist der Name des Makros, das einen Ausdruck (als Liste dargestellt) annimmt und ihn umkehrt. Hier ist wieder "Hallo Welt", diesmal mit dem Makro:
(backwards ("hello, world" t format))
Wenn der Lisp-Compiler diese Codezeile sieht, schaut er auf das erste Atom in der Liste (
backwards
) und bemerkt, dass er ein Makro benennt. Die nicht ausgewertete Liste wird("hello, world" t format)
an das Makro übergeben, in das die Liste neu angeordnet wird(format t "hello, world")
. Die resultierende Liste ersetzt den Makroausdruck und wird zur Laufzeit ausgewertet. Die Lisp-Umgebung erkennt, dass es sich bei ihrem ersten atom (format
) um eine Funktion handelt, wertet sie aus und übergibt ihr die restlichen Argumente.
In Lisp ist diese Aufgabe einfach (korrigieren Sie mich, wenn ich falsch liege), da Code als Liste ( s-Ausdrücke ?) Implementiert ist .
Schauen Sie sich jetzt dieses OCaml-Snippet an (das keine homoikonische Sprache ist):
let print () =
let message = "Hello world" in
print_endline message
;;
Stellen Sie sich vor, Sie möchten OCaml, das im Vergleich zu Lisp eine viel komplexere Syntax verwendet, eine Homoikonizität verleihen. Wie würdest du das machen? Muss die Sprache eine besonders einfache Syntax haben, um Homoikonizität zu erreichen?
EDIT : Von diesem Thema aus habe ich einen anderen Weg gefunden, um Homoikonizität zu erreichen, der sich von dem von Lisp unterscheidet: den in der io-Sprache implementierten . Es kann diese Frage teilweise beantworten.
Beginnen wir hier mit einem einfachen Block:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
Okay, der Block funktioniert also. Der Plusblock fügte zwei Zahlen hinzu.
Lassen Sie uns nun einen Blick auf diesen kleinen Kerl werfen.
Io> plus argumentNames ==> list("a", "b") Io> plus code ==> block(a, b, a +(b)) Io> plus message name ==> a Io> plus message next ==> +(b) Io> plus message next name ==> +
Heiße heilige kalte Form. Sie können nicht nur die Namen der Blockparameter abrufen. Und Sie können nicht nur eine Zeichenfolge des vollständigen Quellcodes des Blocks abrufen. Sie können sich in den Code einschleichen und die darin enthaltenen Nachrichten durchlaufen. Und das Erstaunlichste von allem: Es ist schrecklich einfach und natürlich. Getreu Ios Suche. Rubys Spiegel kann nichts davon sehen.
Aber, whoa whoa, hey jetzt, rühr das Zifferblatt nicht an.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1
quelle
Antworten:
Sie können jede Sprache homoikonisch machen. Im Wesentlichen tun Sie dies, indem Sie die Sprache 'spiegeln' (dh für jeden Sprachkonstruktor fügen Sie eine entsprechende Darstellung dieses Konstruktors als Daten hinzu, denken Sie an AST). Sie müssen auch einige zusätzliche Vorgänge wie das Zitieren und das Aufheben des Zitierens hinzufügen. Das ist mehr oder weniger alles.
Lisp hatte das schon früh aufgrund seiner einfachen Syntax, aber die MetaML-Sprachfamilie von W. Taha zeigte, dass es für jede Sprache möglich ist.
Der gesamte Prozess wird in Modellierung der homogenen generativen Metaprogrammierung beschrieben . Eine leichtere Einführung in dasselbe Material finden Sie hier .
quelle
Der Ocaml-Compiler ist in Ocaml selbst geschrieben, daher gibt es mit Sicherheit eine Möglichkeit, Ocaml-ASTs in Ocaml zu manipulieren.
Man könnte sich vorstellen
ocaml_syntax
, der Sprache einen eingebauten Typ hinzuzufügen und einedefmacro
eingebaute Funktion zu haben, die beispielsweise eine Eingabe von Typ akzeptiertWas ist nun die Art von
defmacro
? Nun, das hängt wirklich von der Eingabe ab, denn selbst wennf
es sich um die Identitätsfunktion handelt, hängt der Typ des resultierenden Codeteils von der übergebenen Syntax ab.Dieses Problem tritt in lisp nicht auf, da die Sprache dynamisch typisiert wird und dem Makro selbst zur Kompilierungszeit kein Typ zugewiesen werden muss. Eine Lösung wäre zu haben
Damit kann das Makro in jedem Kontext verwendet werden. Dies ist jedoch nicht sicher, da es die
bool
Verwendung von a anstelle von a ermöglichtstring
und das Programm zur Laufzeit abstürzt.Die einzige prinzipielle Lösung in einer statisch typisierten Sprache wäre, abhängige Typen zu haben , bei denen der Ergebnistyp von
defmacro
der Eingabe abhängt. An dieser Stelle wird es jedoch ziemlich kompliziert, und ich möchte Sie zunächst auf die schöne Dissertation von David Raymond Christiansen hinweisen .Fazit: Komplizierte Syntax ist kein Problem, da es viele Möglichkeiten gibt, die Syntax innerhalb der Sprache darzustellen und möglicherweise Metaprogramme wie eine
quote
Operation zu verwenden, um die "einfache" Syntax in die interne einzubettenocaml_syntax
.Das Problem ist, dass dies gut typisiert ist, insbesondere mit einem Laufzeitmakromechanismus, der keine Tippfehler zulässt.
Ein Mechanismus zur Kompilierung von Makros in einer Sprache wie Ocaml ist natürlich möglich, siehe zB MetaOcaml .
Ebenfalls möglicherweise nützlich: Jane Street über Metaprogrammierung in Ocaml
quelle
Betrachten Sie als Beispiel F # (basierend auf OCaml). F # ist nicht vollständig homoikonisch, unterstützt jedoch das Abrufen des Codes einer Funktion als AST unter bestimmten Umständen.
In F # wird Ihr
print
als das dargestelltExpr
, das gedruckt wird als:Um die Struktur besser hervorzuheben, gibt es eine alternative Möglichkeit, wie Sie dieselbe erstellen können
Expr
:quelle
eval(<string>)
Funktion verwenden können? ( Vielen Quellen zufolge unterscheidet sich die Eval-Funktion von der Homoikonizität. Ist dies der Grund, warum Sie gesagt haben, dass F # nicht vollständig homoikonisch ist?)print
mit dem[<ReflectedDefinition>]
Attribut markieren .)