Wenn Sie nur eine statisch typisierte Sprache wollten, die wie Lisp aussah , können Sie dies ziemlich einfach tun, indem Sie einen abstrakten Syntaxbaum definieren, der Ihre Sprache darstellt, und diesen AST dann S-Ausdrücken zuordnen. Ich glaube jedoch nicht, dass ich das Ergebnis Lisp nennen würde.
Wenn Sie etwas möchten, das neben der Syntax auch Lisp-y-Eigenschaften aufweist, können Sie dies mit einer statisch typisierten Sprache tun. Es gibt jedoch viele Eigenschaften von Lisp, aus denen sich nur schwer nützliche statische Eingaben ableiten lassen. Schauen wir uns zur Veranschaulichung die Listenstruktur selbst an, die als Nachteile bezeichnet wird und den Hauptbaustein von Lisp bildet.
Die Nachteile als Liste zu bezeichnen, obwohl es so (1 2 3)
aussieht, ist eine Fehlbezeichnung. Zum Beispiel ist es überhaupt nicht vergleichbar mit einer statisch typisierten Liste wie der von C ++ std::list
oder Haskell. Dies sind eindimensionale verknüpfte Listen, in denen alle Zellen vom gleichen Typ sind. Lisp erlaubt gerne (1 "abc" #\d 'foo)
. Selbst wenn Sie Ihre statisch typisierten Listen auf Listenlisten erweitern, erfordert der Typ dieser Objekte, dass jedes Element der Liste eine Unterliste ist. Wie würden Sie ((1 2) 3 4)
in ihnen vertreten?
Lisp-Konsen bilden einen binären Baum mit Blättern (Atomen) und Zweigen (Konsen). Außerdem können die Blätter eines solchen Baumes überhaupt einen atomaren (Nicht-Nachteile) Lisp-Typ enthalten! Die Flexibilität dieser Struktur macht Lisp so gut im Umgang mit symbolischen Berechnungen, ASTs und der Transformation von Lisp-Code selbst!
Wie würden Sie eine solche Struktur in einer statisch typisierten Sprache modellieren? Versuchen wir es in Haskell, das über ein äußerst leistungsfähiges und präzises statisches Typsystem verfügt:
type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons
| CAtom Atom
Ihr erstes Problem wird der Umfang des Atom-Typs sein. Es ist klar, dass wir keinen Atom-Typ mit ausreichender Flexibilität ausgewählt haben, um alle Arten von Objekten abzudecken, die wir in Kegeln herumschleudern möchten. Anstatt zu versuchen, die Atom-Datenstruktur wie oben aufgeführt zu erweitern (was Sie deutlich sehen können, ist spröde), nehmen wir an, wir hatten eine magische Typklasse Atomic
, die alle Typen unterschied, die wir atomar machen wollten. Dann könnten wir versuchen:
class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons
| CAtom a
Dies funktioniert jedoch nicht, da alle Atome im Baum vom gleichen Typ sein müssen. Wir möchten, dass sie sich von Blatt zu Blatt unterscheiden können. Ein besserer Ansatz erfordert die Verwendung von Haskells existenziellen Quantifizierern :
class Atomic a where ?????
data Cons = CCons Cons Cons
| forall a. Atomic a => CAtom a
Aber jetzt kommen Sie zum Kern der Sache. Was kann man mit Atomen in dieser Art von Struktur machen? Welche Struktur haben sie gemeinsam, mit der modelliert werden könnte Atomic a
? Welches Maß an Typensicherheit ist Ihnen bei einem solchen Typ garantiert? Beachten Sie, dass wir unserer Typklasse keine Funktionen hinzugefügt haben, und es gibt einen guten Grund: Die Atome haben in Lisp nichts gemeinsam. Ihr Supertyp in Lisp wird einfach t
(dh oben) genannt.
Um sie zu verwenden, müssten Sie Mechanismen entwickeln, um den Wert eines Atoms dynamisch auf etwas zu zwingen, das Sie tatsächlich verwenden können. Und zu diesem Zeitpunkt haben Sie im Grunde ein dynamisch typisiertes Subsystem in Ihrer statisch typisierten Sprache implementiert! (Man kann nicht anders, als eine mögliche Folge von Greenspuns zehnter Programmierregel zu bemerken .)
Beachten Sie, dass Haskell ein solches dynamisches Subsystem mit einem Obj
Typ unterstützt, der in Verbindung mit einem Dynamic
Typ und einer typisierbaren Klasse verwendet wird , um unsere Atomic
Klasse zu ersetzen , sodass beliebige Werte mit ihren Typen gespeichert werden können, und expliziten Zwang von diesen Typen zurückzwingt . Dies ist die Art von System, die Sie benötigen würden, um mit Lisp-Cons-Strukturen in ihrer vollen Allgemeinheit zu arbeiten.
Sie können auch in die andere Richtung gehen und ein statisch typisiertes Subsystem in eine im Wesentlichen dynamisch typisierte Sprache einbetten. Dies bietet Ihnen den Vorteil einer statischen Typprüfung für die Teile Ihres Programms, die strengere Typanforderungen nutzen können. Dies scheint der Ansatz zu sein, der beispielsweise in der begrenzten Form der präzisen Typprüfung von CMUCL verfolgt wird .
Schließlich besteht die Möglichkeit, zwei separate Subsysteme zu haben, die dynamisch und statisch typisiert sind und die vertragliche Programmierung verwenden, um den Übergang zwischen beiden zu steuern. Auf diese Weise kann die Sprache die Verwendung von Lisp berücksichtigen, bei der die statische Typprüfung eher ein Hindernis als eine Hilfe darstellt, sowie Anwendungen, bei denen die statische Typprüfung vorteilhaft wäre. Dies ist der Ansatz von Typed Racket , wie Sie den folgenden Kommentaren entnehmen können .