Programmiersprachen mit einem Lisp-ähnlichen Syntaxerweiterungsmechanismus [geschlossen]

20

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.

Giorgio
quelle
6
Lisp hat neben normalen Makros noch einen anderen Trick. Mit "Reader Macros" kann die Parsersyntax zur Laufzeit erfasst und erweitert werden, sodass auch die grundlegende Token-Struktur der Sprache unter Kontrolle bleibt.
Ddyer
@ddyer: Danke, das wusste ich nicht: ein weiteres Thema, das zu meiner Leseliste hinzugefügt werden soll.
Giorgio
Ich würde wetten, dass Ruby-Metaprogrammierung dies erfüllen kann.
Rig
1
deine frage ist widersprüchlich, erst fragst du nach einer liste, dann fragst du nach konzeptuellen informationen, was ist das ???
Ich bitte um eine Liste, aber keine generische (nicht irgendeine Programmiersprache, die auf irgendeine Weise erweitert werden kann), weil dies zu allgemein wäre. Ich würde gerne Sprachen kennen, die auf ähnliche Weise wie Lisp erweitert werden können (die Erweiterung wird mit derselben Sprache definiert, die sie erweitert, und nicht mit einer Art Metasprache). Die Antworten von Péter Török, Thomas Eding, SK-logic und Mechanical snail scheinen mir am nächsten zu kommen. Trotzdem muss ich die ganze Antwort noch sorgfältig durchlesen.
Giorgio,

Antworten:

19

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 *:

  • keine Operatoren - alles ist eine Funktion, aber Funktionsnamen können Sonderzeichen wie '+', '-' oder ':' enthalten
  • Punkte und geschweifte Klammern können für Einzelparameter-Methodenaufrufe weggelassen werden, dh sie a.and(b)entsprechen a and bin Infixform
  • Fü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

    val file = new File("example.txt")
    
    withPrintWriter(file) {
      writer => writer.println("this line is a function call parameter")
    }
    

    Dabei withPrintWriterhandelt es sich um eine einfache Methode mit zwei Parameterlisten, die jeweils einen einzelnen Parameter enthalten

  • Mit Parametern nach Namen können Sie leere Parameterlisten in Lambdas weglassen und Anrufe wie myAssert(() => 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.

Péter Török
quelle
1
+1: Interessant. Bedeutet dies, dass ich zur Erweiterung der Syntax neue Klassen definieren kann und dass Methodensignaturen die neue Syntax als Nebenprodukt bereitstellen?
Giorgio
@ Giorgio, im Grunde ja.
Péter Török
Die Links funktionieren nicht mehr.
Nate Glenn
13

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 .

user40980
quelle
13

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:

moveSegment :: [Token] -> Maybe (SegPath, SegPath, [Token])
moveSegment [hc| HC_Move_Segment(@s, @s); | s1 s2 ts |] = Just (mkPath s1, mkPath s2, ts)
moveSegment _ = Nothing

[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.

Thomas Eding
quelle
3
Es gibt auch andere Funktionen von Haskell, die im Wesentlichen eine benutzerdefinierte Syntax ermöglichen, wie das Erstellen eines eigenen Operators oder das automatische Aufrufen in Verbindung mit Lambda-Funktionen (z. B. die typische Verwendung von forM).
Xion
Ich denke ehrlich, dass Currys und benutzerdefinierte Operatoren nicht qualifiziert sind. Sie erlauben es einem, die Sprache ordentlich zu benutzen, aber sie erlauben es nicht, der Sprache / new / Funktionalität hinzuzufügen. TH und QQ tun. Im engeren Sinne tun TH und QQ auch nicht in dem Sinne, dass sie genau das tun, wofür sie entwickelt wurden, aber sie ermöglichen es Ihnen, beim Kompilieren wirklich "aus der Sprache heraus" zu gehen.
Thomas Eding
1
"Ansonsten gilt Folgendes als Syntaxerweiterung ...": sehr guter Punkt.
Giorgio
12

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:

proc loopCard23 {cardinalVar squareVar cubeVar body} {
    upvar 1 $cardinalVar cardinal $squareVar square $cubeVar cube

    # We borrow a 'for' loop for the implementation...
    for {set cardinal 0} true {incr cardinal} {
        set square [expr {$cardinal ** 2}]
        set cube [expr {$cardinal ** 3}]

        uplevel 1 $body
    }
}

Das würde dann so verwendet werden:

loopCard23 a b c {
    puts "got triplet: $a, $b, $c"
    if {$c > 400} {
        break
    }
}

Diese Art von Technik wird in der Tcl-Programmierung häufig verwendet. Der Schlüssel dazu sind die Befehle upvarund uplevel( upvarbindet eine benannte Variable in einem anderen Bereich an eine lokale Variable und uplevelführt ein Skript in einem anderen Bereich aus. In beiden Fällen 1zeigt 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.)

Donal Fellows
quelle
10

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:

  • Viele ältere Sprachen haben eingebaute Funktionen wie sin(), round()etc. bereitgestellt , ohne eigene implementieren zu können.
  • C ++ bietet eingeschränkte Unterstützung. Zum Beispiel einige integrierte Schlüsselwörter wie Abgüsse ( static_cast<target_type>(input), dynamic_cast<>(), const_cast<>(), reinterpret_cast<>()) können Template - Funktionen emuliert werden verwendet, die Boost - Anwendungen für lexical_cast<>(), polymorphic_cast<>(), any_cast<>(), ....
  • Java verfügt über eine integrierte in Kontrollstrukturen ( 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.)

Mechanische Schnecke
quelle
2
Falsch. Lisp ist tatsächlich ermöglicht es Ihnen , absolut jede Art von einer neuen Syntax hinzuzufügen. Wie in diesem Beispiel: meta-alternative.net/pfront.pdf - es ist nichts anderes als ein Lisp-Makro.
SK-logic,
Dies scheint in einer Sprache implementiert zu sein, die speziell für den Aufbau von DSLs entwickelt wurde . Natürlich können Sie eine Sprache in der Lisp-Familie erstellen, die auch solche Funktionen bietet. Ich habe gemeint, dass diese Funktion keine Kernidee von Lisp ist, die von häufig verwendeten Lisps unterstützt wird (z. B. Schema). Zur Verdeutlichung bearbeitet.
Mechanische Schnecke
Diese "Sprache zum Erstellen von DSLs" ist eine Sammlung von Makros, die auf einem ziemlich typischen, minimalistischen Lisp aufbauen. Es kann einfach mit auf jedes andere Lisp portiert werden (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.
SK-logic
Und Scheme unterscheidet sich nicht von Common Lisp, beginnend mit R6RS, offiziell und inoffiziell seit Ewigkeiten, da es ein (define-macro ...)Äquivalent bietet, das wiederum jede Art von internem Parsen verwenden kann.
SK-logic
8

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 ifund whilewenn Sie es wirklich wollen). Dies ist zum Beispiel eine ifAussage:

if now/time < 12:00 [print "Morning"]

ifist 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":

SomeArray: [ [foo "One"] [bar "Two"] [baz "Three"] ]
foreach action SomeArray [action/1: 'print] ; Change the data
if now/time < 12:00 SomeArray/2 ; Use the data as code - right now, if now/time < 12:00 [print "Two"]

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.

Izkata
quelle
7

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 .

knut
quelle
13
"Flexible Syntax" unterscheidet sich SEHR von "erweiterbarer Syntax". Es ist schon lange her, dass ich in Ruby programmiert habe, aber Rake sieht aus wie eine gut durchdachte Verwendung der eingebauten Syntax. Mit anderen Worten, dies ist kein Beispiel.
Thomas Eding
2
Ist das nicht eine Frage des Grades, nicht der Art? Wie würden Sie zwischen einer "erweiterbaren Syntax" -Sprache, die einige Aspekte ihrer Syntax erweitern kann, und einer "flexiblen Syntax" -Sprache unterscheiden?
Kommensturm
1
Wenn die Zeile verschwommen ist, lassen Sie uns die Zeile zurückschieben, sodass C die erweiterbare Syntax unterstützt.
Thomas Eding
Um mit Ihrem Beispiel zu verknüpfen, würde ich hier die Grenze ziehen: Eine Sprache mit erweiterbarer Syntax kann Rake erzeugen, was wie make aussieht. Eine Sprache mit flexibler Syntax kann erweitert werden, um Makefiles zu kompilieren und auszuführen. Ihr Punkt in Bezug auf den Abschluss ist jedoch gut. Vielleicht erlauben einige Sprachen das Kompilieren von Make, aber nicht von Python. Und andere würden beides erlauben.
Clayton Stanley
Jede vollständige Sprache sollte in der Lage sein, Make-Dateien zu verarbeiten. Die Flexibilität der Syntax ist kein Faktor, der über die Herausforderung hinausgeht.
Rig
7

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:

y ~ a + b

bedeutet "eine Linie der Form k0 + k1 * a + k2 * b an die Werte in y anpassen".

y ~ a * b

bedeutet "Passe eine Linie der Form k0 + k1 * a + k2 * b + k3 * a * b an die Werte in y an."

Und so weiter.

Martin C. Martin
quelle
2
Groovys AST-Transformationen sind im Vergleich zu Lisp- oder Clojure-Makros sehr ausführlich. Zum Beispiel könnte das Groovy-Beispiel mit mehr als 20 Zeilen unter groovy.codehaus.org/Global+AST+Transformations als eine kurze Zeile in Clojure umgeschrieben werden, z. B. `(this (println ~ message)). Darüber hinaus müssten Sie das Jar nicht kompilieren, die Metadaten oder andere Dinge auf dieser Groovy-Seite schreiben.
Vorg van Geir
7

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

SK-Logik
quelle
Ihr Scala-Link besagt ausdrücklich, dass die vorgeschlagenen Makros "die Syntax von Scala nicht ändern können". (Interessanterweise wird dies als Unterschied zwischen diesem Vorschlag und C / C ++ - Präprozessor-Makros aufgeführt!)
ruakh
@ruakh, ja, es ist der gleiche Ansatz wie bei Converge und Template Haskell - die Makroanwendung ist explizit markiert und kann nicht mit einer "normalen" Syntax gemischt werden. Aber Sie können jede Syntax verwenden, die Sie möchten. Es handelt sich also wahrscheinlich um eine erweiterbare Syntax. Die "non-lisp" -Anforderung beschränkt Ihre Optionen leider auf Sprachen wie diese.
SK-logic
"Ich habe es sogar mit C gemacht": Wie ist das möglich?
Giorgio,
@ Giorgio, natürlich habe ich einen Compiler modifiziert (hinzugefügte Makros und eine inkrementelle Modulkompilierung, was für C eigentlich ganz natürlich ist).
SK-logic
Warum ist der Zugang zum AST notwendig?
Elliot Gorokhovsky
6

In Prolog können neue Operatoren definiert werden, die in zusammengesetzte Begriffe mit demselben Namen übersetzt werden. Dies definiert beispielsweise einen has_catOperator und definiert ihn als Prädikat für die Überprüfung, ob eine Liste das Atom enthält cat:

:- op(500, xf, has_cat).
X has_cat :- member(cat, X).

?- [apple, cat, orange] has_cat.
true ;
false.

Das xfbedeutet, dass has_cates sich um einen Postfix-Operator handelt. Mit fxwürde es zu einem Präfixoperator und xfxmit zwei Argumenten zu einem Infixoperator. Weitere Informationen zum Definieren von Operatoren in Prolog finden Sie unter diesem Link .

Ambroz Bizjak
quelle
5

TeX fehlt völlig in der Liste. Ihr alle wisst es, richtig? Es sieht ungefähr so ​​aus:

Some {\it ``interesting''} example.

… 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:

Some \it[``interesting''] example.

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:

Some <it>“interesting”</it> example.

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:

\foreach \angle in {0, 30, ..., 330} 
  \draw[line width=1pt] (\angle:0.82cm) -- (\angle:1cm);

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

for word in [Some interesting example.]:
    if word == interesting:
        it(word)
    else:
        word
Konrad Rudolph
quelle
5

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:

  • Das Arbeiten mit einem AST ist nicht so elegant wie das Arbeiten mit S-Ausdrücken
  • Da das Makro zur Kompilierungszeit aufgerufen wird, hat es keinen Zugriff auf Laufzeitdaten.

So können Sie beispielsweise eine neue Kontrollstruktur implementieren:

macro repeatLines(repeatCount as int, lines as string*):
    for line in lines:
        yield [| print $line * $repeatCount |]

Verwendung:

repeatLines 2, "foo", "bar"

welches dann zur Kompilierungszeit in etwas übersetzt wird, wie:

print "foo" * 2
print "bar" * 2

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

nikie
quelle
1
Die Webdokumentation für diese Funktion ist derzeit fehlerhaft, und ich schreibe Boo nicht selbst, aber ich dachte, es wäre schade, wenn dies übersehen würde. Ich freue mich über das Feedback zu den Mods und werde überdenken, wie ich in meiner Freizeit kostenlose Informationen einbringe.
Dan
4

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

fuzzy[a_ && b_]      := Min[fuzzy[a], fuzzy[b]]
fuzzy[a_ || b_]      := Max[fuzzy[a], fuzzy[b]]
fuzzy[!a_]           := 1-fuzzy[a]
If[fuzzy[a_], b_,c_] := fuzzy[a] * fuzzy[b] + fuzzy[!a] * fuzzy[c]

Dies überschreibt die Auswertung für die vordefinierten logischen Operatoren &&, || ,! und die eingebaute IfKlausel.

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:

myIf[True, then_, else_] := then
myIf[False, then_, else_] := else
SetAttributes[myIf, HoldRest]

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.

nikie
quelle
3

Metalua ist eine Sprache und ein Compiler, der mit Lua kompatibel ist und dies bietet.

  • Volle Kompatibilität mit Lua 5.1-Quellen und Bytecode: saubere, elegante Semantik und Syntax, erstaunliche Ausdruckskraft, gute Leistungen, nahezu universelle Portabilität. - Ein vollständiges Makrosystem, ähnlich dem, was Lisp-Dialekte oder Template Haskell bieten. Manipulierte Programme können
    als Quellcode, als abstrakte Syntaxbäume oder als beliebige Mischung
    daraus betrachtet werden, je nachdem, was für Ihre Aufgabe besser geeignet ist.
  • Ein dynamisch erweiterbarer Parser, mit dem Sie Ihre Makros mit einer Syntax unterstützen können, die sich gut in den Rest der Sprache einfügt.

  • Eine Reihe von Spracherweiterungen, die alle als reguläre Metalua-Makros implementiert sind.

Unterschiede zu Lisp:

  • Belästigen Sie Entwickler nicht mit Makros, wenn sie keine Makros schreiben: Die Syntax und Semantik der Sprache sollten in 95% der Fälle am besten geeignet sein, in denen wir keine Makros schreiben.
  • Ermutigen Sie Entwickler, die Konventionen der Sprache zu befolgen: Nicht nur mit "Best Practices Rants", auf die niemand hört, sondern mit einer API, die es einfacher macht, Dinge auf Metalua-Weise zu schreiben. Die Lesbarkeit durch andere Entwickler ist wichtiger und schwieriger zu erreichen als die Lesbarkeit durch Compiler, und dafür ist es sehr hilfreich, über eine Reihe von anerkannten Konventionen zu verfügen.
  • Stellen Sie jedoch alle Kräfte bereit, die Sie bereit sind, zu handhaben. Weder Lua noch Metalua sind zu Gebundenheit und Disziplin verpflichtet. Wenn Sie also wissen, was Sie tun, wird Ihnen die Sprache nicht in die Quere kommen.
  • Machen Sie es deutlich, wenn etwas Interessantes passiert: Alle Meta-Operationen finden zwischen + {...} und - {...} statt und ragen optisch aus dem regulären Code heraus.

Ein Anwendungsbeispiel ist die Implementierung eines ML-ähnlichen Mustervergleichs.

Siehe auch: http://lua-users.org/wiki/MetaLua

Clement J.
quelle
Obwohl Lua in der Tat kein Lisp ist, ist Ihre Liste der "Unterschiede" ziemlich verdächtig (der einzig relevante Punkt ist der letzte). Und natürlich würde die Lisp-Community einer Parole nicht zustimmen, dass man in 95% der Fälle keine Makros schreiben / verwenden sollte Kurs.
SK-logic,
2

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!

Bernat Romagosa
quelle
1
Hört sich interessant an.
Thomas Eding
1

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

Liori
quelle
1

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 .

Dave Kirby
quelle
0

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

Thomas Eding
quelle
Warum haben Sie dies separat gepostet, anstatt es Ihrer vorherigen Antwort hinzuzufügen ?
gnat
1
Weil Funge nichts mit Haskell zu tun hat.
Thomas Eding
-1

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.

mike30
quelle