Ich habe nur begrenzte Kenntnisse in Lisp (versuche in meiner Freizeit ein bisschen zu lernen), aber soweit ich Lisp-Makros verstehe, können neue Sprachkonstrukte und Syntax eingeführt werden, indem sie in Lisp selbst beschrieben werden. Dies bedeutet, dass ein neues Konstrukt als Bibliothek hinzugefügt werden kann, ohne den Lisp-Compiler / -Interpreter zu ändern.
Dieser Ansatz unterscheidet sich stark von dem anderer Programmiersprachen. Wenn ich beispielsweise Pascal mit einer neuen Art von Schleife oder einer bestimmten Redewendung erweitern wollte, müsste ich die Syntax und Semantik der Sprache erweitern und dann diese neue Funktion im Compiler implementieren.
Gibt es andere Programmiersprachen außerhalb der Lisp-Familie (außer Common Lisp, Scheme, Clojure (?), Racket (?) Usw.), die eine ähnliche Möglichkeit bieten, die Sprache innerhalb der Sprache selbst zu erweitern?
BEARBEITEN
Vermeiden Sie bitte längere Diskussionen und geben Sie Ihre Antworten genau an. Anstelle einer langen Liste von Programmiersprachen, die auf die eine oder andere Weise erweitert werden können, möchte ich aus konzeptioneller Sicht verstehen, was für Lisp-Makros als Erweiterungsmechanismus spezifisch ist und welche Nicht-Lisp-Programmiersprachen ein Konzept bieten das ist nah an ihnen.
quelle
Antworten:
Scala macht dies auch möglich (tatsächlich wurde es bewusst entwickelt, um die Definition neuer Sprachkonstrukte und sogar vollständiger DSLs zu unterstützen).
Abgesehen von Funktionen höherer Ordnung, Lambdas und Currying, die in funktionalen Sprachen üblich sind, gibt es hier einige spezielle Sprachmerkmale, die dies ermöglichen *:
a.and(b)
entsprechena and b
in InfixformFür Funktionsaufrufe mit einem Parameter können Sie geschweifte Klammern anstelle von normalen Klammern verwenden - dies ermöglicht Ihnen (zusammen mit dem Curry) das Schreiben von Dingen wie
Dabei
withPrintWriter
handelt es sich um eine einfache Methode mit zwei Parameterlisten, die jeweils einen einzelnen Parameter enthaltenmyAssert(() => x > 3)
in einer kürzeren Form als schreibenmyAssert(x > 3)
Die Erstellung eines DSL-Beispiels wird ausführlich in Kapitel 11 beschrieben. Domänenspezifische Sprachen in Scala aus dem kostenlosen Buch Programming Scala .
* Ich meine nicht, dass diese nur in Scala vorkommen, aber zumindest scheinen sie nicht sehr verbreitet zu sein. Ich bin jedoch kein Experte für funktionale Sprachen.
quelle
Perl ermöglicht die Vorverarbeitung seiner Sprache. Dies wird zwar nicht oft verwendet, um die Syntax in der Sprache radikal zu ändern, ist jedoch in einigen der ... ungeraden Module zu sehen:
Es gibt auch ein Modul, mit dem Perl Code ausführen kann, der aussieht, als wäre er in Python geschrieben worden.
Ein moderner Ansatz in Perl wäre die Verwendung von Filter :: Simple (eines der Kernmodule in Perl5).
Beachten Sie, dass es sich bei all diesen Beispielen um Damian Conway handelt, der als "Mad Doctor of Perl" bezeichnet wurde. Es ist immer noch eine erstaunlich mächtige Fähigkeit in Perl, die Sprache so zu verändern, wie man es will.
Weitere Dokumentation für diese und andere Alternativen finden Sie unter perlfilter .
quelle
Haskell
Haskell hat "Template Haskell" sowie "Quasiquotation":
http://www.haskell.org/haskellwiki/Template_Haskell
http://www.haskell.org/haskellwiki/Quasiquotation
Diese Funktionen ermöglichen es Benutzern, die Syntax der Sprache außerhalb der normalen Mittel dramatisch zu erweitern. Diese werden auch zur Kompilierungszeit behoben, was meiner Meinung nach ein großes Muss ist (zumindest für kompilierte Sprachen) [1].
Ich habe in Haskell schon einmal Quasiquotation verwendet, um einen erweiterten Mustervergleich für eine C-ähnliche Sprache zu erstellen:
[1] Andernfalls gilt Folgendes als Syntaxerweiterung:
runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"
Dies ist natürlich eine Menge Mist.quelle
forM
).Tcl unterstützt seit langem erweiterbare Syntax. Hier ist zum Beispiel die Implementierung einer Schleife, die drei Variablen (bis zum Stillstand) über die Kardinäle, ihre Quadrate und ihre Würfel iteriert:
Das würde dann so verwendet werden:
Diese Art von Technik wird in der Tcl-Programmierung häufig verwendet. Der Schlüssel dazu sind die Befehle
upvar
unduplevel
(upvar
bindet eine benannte Variable in einem anderen Bereich an eine lokale Variable unduplevel
führt ein Skript in einem anderen Bereich aus. In beiden Fällen1
zeigt das an der fragliche Bereich ist der Anrufer). Es wird auch häufig in Code verwendet, der mit Datenbanken gekoppelt ist (wobei Code für jede Zeile in einer Ergebnismenge ausgeführt wird), in Tk für GUIs (zum Binden von Rückrufen an Ereignisse) usw.Dies ist jedoch nur ein Bruchteil dessen, was getan wird. Die eingebettete Sprache muss nicht einmal Tcl sein. es kann praktisch alles sein (solange es seine Klammern ausbalanciert - wenn das nicht stimmt, werden die Dinge syntaktisch schrecklich - das ist die enorme Mehrheit der Programme) und Tcl kann bei Bedarf einfach in die eingebettete Fremdsprache senden. Beispiele hierfür sind die Einbettung von C zur Implementierung von Tcl-Befehlen und das Äquivalent zu Fortran. (Vermutlich werden alle in Tcl integrierten Befehle auf diese Weise ausgeführt, da sie eigentlich nur eine Standardbibliothek sind und nicht die Sprache selbst.)
quelle
Dies ist zum Teil eine Frage der Semantik. Die Grundidee von Lisp ist, dass es sich bei dem Programm um Daten handelt, die selbst manipuliert werden können. Häufig verwendete Sprachen in der Lisp-Familie wie Scheme lassen Sie keine neue Syntax im Parser-Sinne hinzufügen . Es sind alles nur durch Leerzeichen begrenzte Listen in Klammern. Es ist nur so, dass Sie fast jedes semantische Konstrukt daraus machen können, da die Kernsyntax so wenig bewirkt . Scala (weiter unten beschrieben) ist ähnlich: Die Regeln für Variablennamen sind so liberal, dass Sie problemlos schöne DSLs daraus machen können (während Sie die gleichen Hauptsyntaxregeln einhalten).
Diese Sprachen ermöglichen zwar nicht das Definieren einer neuen Syntax im Sinne von Perl-Filtern, verfügen jedoch über einen ausreichend flexiblen Kern, mit dem Sie DSLs erstellen und Sprachkonstrukte hinzufügen können.
Das wichtige gemeinsame Merkmal ist, dass Sie damit sowohl funktionierende als auch integrierte Sprachkonstrukte definieren können, indem Sie Funktionen verwenden, die von den Sprachen verfügbar gemacht werden. Der Grad der Unterstützung für diese Funktion variiert:
sin()
,round()
etc. bereitgestellt , ohne eigene implementieren zu können.static_cast<target_type>(input)
,dynamic_cast<>()
,const_cast<>()
,reinterpret_cast<>()
) können Template - Funktionen emuliert werden verwendet, die Boost - Anwendungen fürlexical_cast<>()
,polymorphic_cast<>()
,any_cast<>()
, ....for(;;){}
,while(){}
,if(){}else{}
,do{}while()
,synchronized(){}
,strictfp{}
) und nicht lassen Sie Ihre eigenen definieren. Scala definiert stattdessen eine abstrakte Syntax, mit der Sie Funktionen mithilfe einer praktischen Syntax wie bei einer Kontrollstruktur aufrufen können. Bibliotheken verwenden diese Syntax, um neue Kontrollstrukturen effektiv zu definieren (z. B.react{}
in der Schauspielerbibliothek).Möglicherweise sehen Sie sich auch die benutzerdefinierten Syntaxfunktionen von Mathematica im Notation-Paket an . (Technisch gesehen gehört es zur Lisp-Familie, hat jedoch einige Erweiterungsfunktionen anders ausgeführt als die übliche Lisp-Erweiterbarkeit.)
quelle
(defmacro ...)
. Eigentlich portiere ich diese Sprache gerade zu Racket, nur zum Spaß. Ich stimme jedoch zu, dass dies nicht sehr nützlich ist, da die Syntax von S-Ausdrücken für die meisten möglichen nützlichen Semantiken mehr als ausreichend ist.(define-macro ...)
Äquivalent bietet, das wiederum jede Art von internem Parsen verwenden kann.Rebol klingt fast wie das, was Sie beschreiben, aber ein bisschen seitwärts.
Anstatt eine bestimmte Syntax zu definieren, ist alles in Rebol ein Funktionsaufruf - es gibt keine Schlüsselwörter. (Ja, Sie können neu definieren
if
undwhile
wenn Sie es wirklich wollen). Dies ist zum Beispiel eineif
Aussage:if
ist eine Funktion, die zwei Argumente akzeptiert: eine Bedingung und einen Block. Wenn die Bedingung erfüllt ist, wird der Block ausgewertet. Klingt nach den meisten Sprachen, oder? Nun, der Block ist eine Datenstruktur, er ist nicht auf Code beschränkt - dies ist beispielsweise ein Block von Blöcken und ein kurzes Beispiel für die Flexibilität von "Code ist Daten":Solange Sie sich an die Syntaxregeln halten können, ist das Erweitern dieser Sprache zum größten Teil nichts anderes als das Definieren neuer Funktionen. Einige Benutzer haben beispielsweise Funktionen von Rebol 3 in Rebol 2 zurückportiert.
quelle
Ruby hat eine recht flexible Syntax, ich denke, es ist eine Möglichkeit, "die Sprache innerhalb der Sprache selbst zu erweitern".
Ein Beispiel ist Rechen . Es ist in Ruby geschrieben, es ist Ruby, aber es sieht aus wie make .
Um einige Möglichkeiten zu prüfen, können Sie nach den Schlüsselwörtern Ruby und Metaprogramm suchen .
quelle
Wenn Sie die Syntax so erweitern, wie Sie es möchten, können Sie domänenspezifische Sprachen erstellen . Der vielleicht nützlichste Weg, um Ihre Frage neu zu formulieren, ist, welche anderen Sprachen eine gute Unterstützung für domänenspezifische Sprachen bieten.
Ruby hat eine sehr flexible Syntax, und viele DSLs, wie z. B. Rake, sind dort aufgetaucht. Groovy beinhaltet eine Menge dieser Güte. Es enthält auch AST-Transformationen, die den Lisp-Makros direkter entsprechen.
R, die Sprache für statistische Berechnungen, ermöglicht es Funktionen, ihre Argumente nicht zu bewerten. Damit wird eine DSL zur Angabe der Regressionsformel erstellt. Beispielsweise:
bedeutet "eine Linie der Form k0 + k1 * a + k2 * b an die Werte in y anpassen".
bedeutet "Passe eine Linie der Form k0 + k1 * a + k2 * b + k3 * a * b an die Werte in y an."
Und so weiter.
quelle
Converge ist eine weitere nicht-lispy Metaprogrammiersprache. In gewissem Maße ist auch C ++ geeignet.
Argumentieren, MetaOCaml ist ziemlich weit von Lisp. Für eine völlig andere Art der Syntaxerweiterung, aber dennoch sehr leistungsfähig, schauen Sie sich CamlP4 an .
Nemerle ist eine weitere erweiterbare Sprache mit einer Lisp-ähnlichen Metaprogrammierung, obwohl sie Sprachen wie Scala ähnlicher ist.
Und auch Scala selbst wird bald eine solche Sprache.
Edit: Ich habe das interessanteste Beispiel vergessen - JetBrains MPS . Es ist nicht nur sehr weit von Lispish entfernt, es ist sogar ein nicht-textuelles Programmiersystem mit einem Editor, der direkt auf AST-Ebene arbeitet.
Edit2: Um eine aktualisierte Frage zu beantworten - es gibt nichts Einzigartiges und Außergewöhnliches in den Lisp-Makros. Theoretisch kann jede Sprache einen solchen Mechanismus bieten (ich habe es sogar mit einfachem C gemacht). Alles, was Sie benötigen, ist ein Zugriff auf Ihren AST und die Fähigkeit, Code in der Kompilierungszeit auszuführen. Einige Überlegungen könnten hilfreich sein (Fragen nach den Typen, den vorhandenen Definitionen usw.).
quelle
In Prolog können neue Operatoren definiert werden, die in zusammengesetzte Begriffe mit demselben Namen übersetzt werden. Dies definiert beispielsweise einen
has_cat
Operator und definiert ihn als Prädikat für die Überprüfung, ob eine Liste das Atom enthältcat
:Das
xf
bedeutet, dasshas_cat
es sich um einen Postfix-Operator handelt. Mitfx
würde es zu einem Präfixoperator undxfx
mit zwei Argumenten zu einem Infixoperator. Weitere Informationen zum Definieren von Operatoren in Prolog finden Sie unter diesem Link .quelle
TeX fehlt völlig in der Liste. Ihr alle wisst es, richtig? Es sieht ungefähr so aus:
… Außer dass Sie die Syntax ohne Einschränkungen neu definieren können. Jedem (!) Token in der Sprache kann eine neue Bedeutung zugewiesen werden. ConTeXt ist ein Makropaket, das geschweifte Klammern durch eckige Klammern ersetzt hat:
Das allgemeinere Makropaket LaTeX definiert auch die Sprache für seine Zwecke neu, z
\begin{environment}…\end{environment}
. B. durch Hinzufügen der Syntax.Aber es hört hier nicht auf. Technisch gesehen können Sie die Token genauso gut neu definieren, um Folgendes zu analysieren:
Ja, absolut möglich Einige Pakete verwenden dies, um kleine domänenspezifische Sprachen zu definieren. Beispielsweise definiert das TikZ- Paket eine kurze Syntax für technische Zeichnungen, die Folgendes ermöglicht:
Darüber hinaus ist TeX Turing komplett, so dass Sie buchstäblich alles damit machen können. Ich habe noch nie erlebt, wie das Potenzial voll ausgeschöpft wurde, weil es ziemlich sinnlos und sehr verworren wäre, aber es ist durchaus möglich, den folgenden Code nur durch Neudefinieren von Token syntaktisch analysierbar zu machen (dies würde jedoch wahrscheinlich aufgrund des Parsers an die physischen Grenzen gehen) wie es gebaut ist):
quelle
Mit Boo können Sie die Sprache während der Kompilierung mithilfe syntaktischer Makros stark anpassen.
Boo hat eine "erweiterbare Compiler-Pipeline". Dies bedeutet, dass der Compiler Ihren Code jederzeit während der Compiler-Pipeline aufrufen kann, um AST-Transformationen durchzuführen. Wie Sie wissen, handelt es sich bei Dingen wie Javas Generics oder C # -Linq lediglich um Syntaxtransformationen zur Kompilierungszeit. Dies ist also recht mächtig.
Der Hauptvorteil gegenüber Lisp ist, dass dies mit jeder Art von Syntax funktioniert. Boo verwendet eine Python-inspirierte Syntax, aber Sie könnten wahrscheinlich einen erweiterbaren Compiler mit einer C- oder Pascal-Syntax schreiben. Und da das Makro zur Kompilierungszeit ausgewertet wird, gibt es keine Leistungseinbußen.
Nachteile im Vergleich zu Lisp sind:
So können Sie beispielsweise eine neue Kontrollstruktur implementieren:
Verwendung:
welches dann zur Kompilierungszeit in etwas übersetzt wird, wie:
(Leider ist die Online-Dokumentation von Boo immer hoffnungslos veraltet und deckt nicht einmal fortgeschrittene Inhalte wie diese ab. Die beste Dokumentation für die Sprache, die ich kenne, ist dieses Buch: http://www.manning.com/rahien/ )
quelle
Die Auswertung von Mathematica basiert auf Mustererkennung und -ersetzung. Auf diese Weise können Sie Ihre eigenen Kontrollstrukturen erstellen, vorhandene Kontrollstrukturen ändern oder die Art und Weise ändern, in der Ausdrücke ausgewertet werden. Zum Beispiel könnten Sie "Fuzzy-Logik" wie folgt implementieren (etwas vereinfacht):
Dies überschreibt die Auswertung für die vordefinierten logischen Operatoren &&, || ,! und die eingebaute
If
Klausel.Sie können diese Definitionen wie Funktionsdefinitionen lesen, aber die wahre Bedeutung ist: Wenn ein Ausdruck mit dem auf der linken Seite beschriebenen Muster übereinstimmt, wird er durch den Ausdruck auf der rechten Seite ersetzt. Sie können Ihre eigene If-Klausel wie folgt definieren:
SetAttributes[..., HoldRest]
teilt dem Bewerter mit, dass er das erste Argument vor dem Mustervergleich auswerten soll, den Rest jedoch auswerten soll, bis das Muster abgeglichen und ersetzt wurde.Dies wird in den Mathematica-Standardbibliotheken häufig verwendet, um z. B. eine Funktion zu definieren
D
, die einen Ausdruck aufnimmt und zu ihrer symbolischen Ableitung auswertet.quelle
Metalua ist eine Sprache und ein Compiler, der mit Lua kompatibel ist und dies bietet.
Unterschiede zu Lisp:
Ein Anwendungsbeispiel ist die Implementierung eines ML-ähnlichen Mustervergleichs.
Siehe auch: http://lua-users.org/wiki/MetaLua
quelle
Wenn Sie nach Sprachen suchen, die erweiterbar sind, sollten Sie sich Smalltalk ansehen.
In Smalltalk besteht die einzige Möglichkeit zum Programmieren darin, die Sprache tatsächlich zu erweitern. Es gibt keinen Unterschied zwischen der IDE, den Bibliotheken oder der Sprache. Sie sind alle so miteinander verwoben, dass Smalltalk oft als Umgebung und nicht als Sprache bezeichnet wird.
Sie schreiben keine eigenständigen Anwendungen in Smalltalk, sondern erweitern die Sprachumgebung.
Unter http://www.world.st/ finden Sie eine Handvoll Ressourcen und Informationen.
Ich möchte Pharo als Einstiegsdialekt in die Welt von Smalltalk empfehlen: http://pharo-project.org
Hoffe es hat geholfen!
quelle
Es gibt Tools, mit denen Sie benutzerdefinierte Sprachen erstellen können, ohne einen ganzen Compiler von Grund auf neu schreiben zu müssen. Zum Beispiel gibt es Spoofax , ein Codetransformationstool : Sie geben Eingabe-Grammatik- und -Transformationsregeln ein (deklarativ auf sehr hoher Ebene geschrieben) und können dann Java-Quellcode generieren (oder eine andere Sprache, wenn Sie dies wünschen). aus einer von Ihnen entworfenen Sprache.
Es wäre also möglich, die Grammatik der Sprache X zu übernehmen, die Grammatik der Sprache X '(X mit Ihren benutzerdefinierten Erweiterungen) und die Transformation X' → X zu definieren, und Spoofax generiert einen Compiler X '→ X.
Wenn ich das richtig verstehe, ist derzeit die beste Unterstützung für Java, wobei die C # -Unterstützung entwickelt wird (wie ich gehört habe). Diese Technik kann jedoch auf jede Sprache mit statischer Grammatik angewendet werden (z. B. wahrscheinlich nicht auf Perl ).
quelle
Forth ist eine andere Sprache, die sehr erweiterbar ist. Viele Forth-Implementierungen bestehen aus einem kleinen Kernel, der in Assembler oder C geschrieben ist, und der Rest der Sprache ist in Forth selbst geschrieben.
Es gibt auch mehrere stapelbasierte Sprachen, die von Forth inspiriert sind und diese Funktion gemeinsam haben, z. B. Factor .
quelle
Funge-98
Die Fingerabdruckfunktion von Funge-98 ermöglicht eine vollständige Umstrukturierung der gesamten Syntax und Semantik der Sprache. Dies ist jedoch nur möglich, wenn der Implementierer einen Fingerabdruckmechanismus bereitstellt, mit dem der Benutzer die Sprache programmgesteuert ändern kann (dies kann theoretisch innerhalb der normalen Funge-98-Syntax und -Semantik implementiert werden). In diesem Fall könnte man den Rest der Datei (oder beliebige Teile der Datei) buchstäblich als C ++ oder Lisp (oder was auch immer er will) verwenden.
http://quadium.net/funge/spec98.html#Fingerprints
quelle
Um das zu bekommen, wonach Sie suchen, benötigen Sie diese Klammern und eine fehlende Syntax. Ein paar syntaxbasierte Sprachen mögen nahe kommen, aber es ist nicht ganz dasselbe wie ein echtes Makro.
quelle