Lisp mit Pascal oder der Programmiersprache 8501. gewürzt

Vor einiger Zeit (ungefähr drei Jahre) habe ich beschlossen, ein Lehrbuch über Lisp zu lesen. Ohne einen bestimmten Zweck, nur um der allgemeinen Entwicklung und der Fähigkeit willen, die Gesprächspartner mit Exotik zu schockieren (sobald es scheint, hat es sogar geklappt).

Bei näherer Betrachtung erwies sich Lisp jedoch als sehr leistungsfähig, flexibel und seltsamerweise nützlich im täglichen Leben. Alle kleineren Automatisierungsaufgaben wurden schnell auf Skripte in Lisp migriert, und es gab auch Möglichkeiten zur Automatisierung komplexerer Aufgaben.

Hierbei ist zu beachten, dass mit „Automatisierungsfähigkeit“ eine Situation gemeint ist, in der die Gesamtzeit zum Schreiben und Debuggen eines Programms geringer ist als die Zeit, die zum manuellen Lösen derselben Aufgabe aufgewendet wird.

Paul Graham hat mehr als einen Artikel und sogar ein Buch über die Vorteile von Lisp geschrieben. Zum Zeitpunkt dieses Schreibens belegt Lisp den 33. Platz in der TOIBE-Rangliste (dreimal tot als tot Delphi). Es stellt sich die Frage: Warum ist die Sprache so klein, wenn sie so bequem ist? Etwa zwei Jahre Gebrauch gaben einige Hinweise auf die Gründe.

Nachteile


1. Gemeinsame Datenstrukturen
Ein Konzept, mit dem Sie funktionale Programme optimieren können, das jedoch mit subtilen Fehlern im Imperativ behaftet ist. Die Möglichkeit einer versehentlichen Beschädigung einer fremden Datenstruktur, wenn eine Variable geändert wird, die keinen sichtbaren Zusammenhang mit der Struktur hat, erfordert, dass der Programmierer ständig überwacht, was hinter den Kulissen geschieht, und die interne Implementierung jeder verwendeten Funktion (sowohl System als auch Benutzer) kennt. Das Erstaunlichste ist die Fähigkeit, den Körper einer Funktion durch Ändern ihres Rückgabewerts zu beschädigen.

2. Fehlende Einkapselung
Obwohl das Konzept eines Pakets existiert, hat es nichts mit einem Paket in Ada oder einer Einheit in Delphi zu tun. Jeder Code kann jedem Paket etwas hinzufügen (außer Systempaketen). Jeder Code kann mit dem Operator :: alles aus jedem Paket extrahieren.

3. Zufällige Abkürzungen
Was ist der Unterschied zwischen MAPCAN und MAPCON? Warum in SETQ der letzte Buchstabe Q? Angesichts des Alters der Sprache können Sie die Gründe für diesen Zustand verstehen, aber ich möchte, dass die Sprache etwas sauberer wird.

4. Multithreading
Dieser Nachteil hängt indirekt mit Lisp zusammen und betrifft hauptsächlich die von mir verwendete Implementierung - SteelBank Common Lisp. Common Lisp unterstützt kein Multithreading. Ein Versuch, die von SBCL bereitgestellte Implementierung zu verwenden, ist fehlgeschlagen.

Es ist schade, solch ein praktisches Werkzeug abzulehnen, aber die Unzufriedenheit nimmt allmählich zu.

Suche nach einer Lösung


Zuerst können Sie auf der Lisp-Seite zu Wikipedia gehen. Überprüfen Sie den Abschnitt "Dialekte". Lesen Sie jeweils eine kurze Einführung. Und stellen Sie fest, dass der Geschmack und die Farbe aller Marker unterschiedlich sind.
Wenn Sie etwas tun möchten, müssen Sie es selbst tun
- Jean Baptiste Emmanuel Sorg
Versuchen wir, unser eigenes korrektes Lisp zu erstellen, indem wir ein wenig Ada, viel Delphi und einen Tropfen Oberon hinzufügen. Wir nennen die resultierende Mischung Fox.

Grundbegriffe


1. Keine Zeiger
Im Kampf gegen PROBLEM-1 müssen alle Operationen durch Kopieren der Werte ausgeführt werden. Durch die Art der Datenstruktur im Code oder beim Drucken sollten alle Eigenschaften, externen und internen Verbindungen vollständig sichtbar sein.

2. Fügen Sie Module hinzu
Im Rahmen des Kampfes gegen Problem 2 importieren wir Pakete mit und verwenden Anweisungen von Ada. Dabei verwerfen wir das übermäßig komplexe Import- / Schattenschema für Lisp-Symbole.
(package - (  ) () ()) 

 (with -) ;  «-.lisya»    

 (use -) ;,       


3. Weniger Abkürzungen
Die gebräuchlichsten und gebräuchlichsten Zeichen werden immer noch abgekürzt, aber meistens die offensichtlichsten: const , var . Formatierte Ausgabefunktion - FMT erfordert eine Reduzierung, wie sie häufig in Ausdrücken vorkommt. Elt - ein Element nehmen - ist aus Common Lisp ausgetreten und hat Wurzeln geschlagen, obwohl es nicht nötig ist, es zu reduzieren.

4. Bezeichner, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird
Ich bin der Meinung, dass die richtige Sprache (und das richtige Dateisystem) {$ HOLYWAR +} die Groß- und Kleinschreibung nicht berücksichtigen sollte {$ HOLYWAR-}, um den Kopf nicht noch einmal zu zerbrechen.

5. Benutzerfreundlichkeit mit dem russischen Tastaturlayout
Die Syntax Lisi vermeidet auf jede mögliche Weise die Verwendung von Zeichen, die in einem der Layouts nicht verfügbar sind. Keine quadratischen oder geschweiften Klammern. Nein #, ~, &, <,>, |. Beim Lesen von numerischen Literalen werden sowohl ein Komma als auch ein Punkt als Dezimaltrennzeichen betrachtet.

6. Erweitertes Alphabet
Eines der schönen Dinge an SBCL war UTF-8 im Code. Die Möglichkeit, die Konstanten BEAR, VODKA und BALALAYKA zu deklarieren, vereinfacht das Schreiben von Anwendungscode erheblich. Die Möglichkeit, Ω, Ψ und Σ einzufügen, macht die Formeln im Code visueller. Obwohl theoretisch die Möglichkeit besteht, Unicode-Zeichen zu verwenden, ist es schwierig, die Richtigkeit der Arbeit mit ihnen zu gewährleisten (eher Faulheit als Schwierigkeit). Wir beschränken uns auf Kyrillisch, Latein und Griechisch.

7. Numerische Literale
Dies ist die nützlichste Spracherweiterung für mich.

 10_000 ;    10k ;       10 ;       10° 10pi 10deg 10 ;   10π ; pi     10+i10 ;   10+10 ;    1010deg ;          

Die letztere Option scheint mir die nicht ästhetischste zu sein, aber sie ist die beliebteste.

8. Zyklen
Die Zyklen in Lisp sind nicht Standard und ziemlich chaotisch. Vereinfachen Sie auf den Mindeststandardsatz.

 (for i 5 ;   i = 0..4 ) (for i 1..6 ;   i = 1..5 ) (for i  ;     ;      ) (for i (subseq  2) ;           ) 

Die Schleifenvariable ist draußen nicht sichtbar.

 (while  ) 

9. GOTO
Kein sehr notwendiger Operator, aber ohne ihn ist es schwierig, die Vernachlässigung der Regeln der strukturellen Programmierung zu demonstrieren.

 (block : (goto :)) ;     

10. Vereinheitlichung des Geltungsbereichs
In Lisp gibt es zwei verschiedene Arten von Gültigkeitsbereichen: TOPLEVEL und local. Dementsprechend gibt es zwei verschiedene Möglichkeiten, Variablen zu deklarieren.

 (defvar A 1) (let ((a 1)) …) 

In Fox gibt es nur eine Methode, die sowohl auf der obersten Ebene des Skripts als auch in lokalen Bereichen, einschließlich Paketen, verwendet wird.

 (var A 1) 

Wenn Sie den Bereich einschränken möchten, verwenden Sie den Operator

 (block (var A 1) (set A 2) (fmt nil A)) 

Der Hauptteil der Schleife ist in der impliziten BLOCK-Anweisung enthalten (wie der Hauptteil der Funktion / Prozedur). Alle in der Schleife deklarierten Variablen werden am Ende der Iteration zerstört.

11. Zeichen mit einem Steckplatz
In Lisp sind Funktionen spezielle Objekte und werden in einem speziellen Symbolsteckplatz gespeichert. Ein einzelnes Zeichen kann gleichzeitig eine Variablen-, Funktions- und Eigenschaftsliste speichern. In einem Fuchs ist jedem Zeichen nur eine Bedeutung zugeordnet.

12. Praktische ELT
Der typische Zugriff auf ein Element komplexer Struktur in Lisp sieht folgendermaßen aus

 (elt (slot-value (elt  1) '-2) 3) 

Der Fox verfügt über einen einheitlichen ELT-Operator, der den Zugriff auf Elemente eines beliebigen zusammengesetzten Typs (Listen, Zeichenfolgen, Datensätze, Bytearrays, Hash-Tabellen) ermöglicht.

 (elt  1 \-2 3) 

Identische Funktionen können auch mit einem Makro in Lisp erhalten werden

 (defmacro field (object &rest f) "       . (field *object* 0 :keyword symbol \"string\")       .        plist.   ( )    .        ." (if f (symbol-macrolet ((f0 (elt f 0))(rest (subseq f 1))) (cond ((numberp f0) `(field (elt ,object ,f0) ,@rest)) ((keywordp f0) `(field (getf ,object ,f0) ,@rest)) ((stringp f0) `(field (cdr (assoc ,f0 ,object :test 'equal)) ,@rest)) ((and (listp f0) (= 2 (length f0))) `(field (,(car f0) ,(cadr f0) ,object) ,@rest)) ((symbolp f0) `(field (,f0 ,object) ,@rest)) (t `(error "   ")))) object)) 

13. Einschränkung der Übertragungsmodi für Unterprogrammparameter
In Lisp gibt es mindestens fünf Modi für die Parameterübertragung: obligatorisch, & optional , & rest , & key , & ganz, und ihre willkürliche Kombination ist zulässig. Tatsächlich ergeben die meisten Kombinationen seltsame Effekte.
In Fox darf nur eine Kombination der erforderlichen Parameter und einer der folgenden Modi zur Auswahl verwendet werden : key ,: optional,: flag ,: rest .

14. Multithreading
Um das Schreiben von Multithread-Programmen so weit wie möglich zu vereinfachen, wurde das Konzept der Speichertrennung übernommen. Wenn ein Thread erzeugt wird, werden alle für den neuen Thread verfügbaren Variablen kopiert. Alle Verweise auf diese Variablen werden durch Verweise auf Kopien ersetzt. Die Informationsübertragung zwischen Streams ist nur über geschützte Objekte oder über das Ergebnis möglich, das der Stream nach Abschluss zurückgibt.

Geschützte Objekte enthalten immer kritische Abschnitte, um atomare Operationen sicherzustellen. Die Anmeldung in kritischen Abschnitten erfolgt automatisch - in der Sprache gibt es hierfür keine separaten Operatoren. Zu den geschützten Objekten gehören: Nachrichtenwarteschlange, Konsole und Dateideskriptoren.

Das Erstellen von Threads ist mit einer Multithread-Anzeigefunktion möglich

 (map-th (function (x) …) --) 

Map-th startet automatisch die Anzahl der Threads, die der Anzahl der Prozessoren im System entspricht (oder doppelt so viele, wenn Intel installiert ist). Bei einem rekursiven Aufruf funktionieren nachfolgende Map-Th-Aufrufe in einem einzelnen Thread.

Zusätzlich gibt es eine integrierte Thread-Funktion, die eine Prozedur / Funktion in einem separaten Thread ausführt.

 ;   (var  (thread --1)) (+ (--2) (wait )) 

15. Funktionale Sauberkeit im Imperativcode
Der Fox hat Funktionen für die funktionale Programmierung und Prozeduren für die Prozedur. Die mit dem Funktionsschlüsselwort deklarierten Routinen unterliegen den Anforderungen der Abwesenheit von Nebenwirkungen und der Unabhängigkeit des Ergebnisses von externen Faktoren.

Nicht realisiert


Einige interessante Merkmale von Lisp blieben aufgrund der geringen Priorität unerfüllt.

1. Verallgemeinerte Methoden
Fähigkeit, Funktionen mit defgeneric / defmethod zu überladen.

2. Vererbung

3. Eingebauter Debugger
Wenn eine Ausnahme auftritt, wechselt der Lisp-Interpreter in den Debug-Modus.

4. UFFI
Schnittstelle zum Anschließen von Modulen, die in anderen Sprachen geschrieben sind.

5. BIGNUM
Beliebige Bittiefenunterstützung

Verworfen

Einige der Funktionen von Lisp wurden als nutzlos / schädlich angesehen.

1. Geführte Kombination von Methoden
Wenn eine Methode für eine Klasse aufgerufen wird, wird eine Kombination übergeordneter Methoden ausgeführt, und es ist möglich, die Kombinationsregeln zu ändern. Das endgültige Verhalten der Methode scheint schlecht vorhersehbar.

2. Startet neu
Der Ausnahmebehandler kann Änderungen am Status des Programms vornehmen und einen Neustartbefehl an den Code senden, der die Ausnahme generiert hat. Der Effekt der Anwendung ähnelt der Verwendung des GOTO-Operators zum Umschalten von Funktion zu Funktion.

3. Der römische Bericht
Lisp unterstützt das kurz vor seinem Erscheinen veraltete Zahlensystem.

Verwenden Sie


Hier sind einige einfache Codebeispiele.

 (function crc8 (data :optional seed) (var result (if-nil seed 0)) (var s_data data) (for bit 8 (if (= (bit-and (bit-xor result s_data) $01) 0) (set result (shift result -1 8)) (else (set result (bit-xor result $18)) (set result (shift result -1 8)) (set result (bit-or result $80)))) (set s_data (shift s_data -1 8))) result) 

 ;     (map (function (x) (** x 2)) \(1 2 3)) 

 ;   ,   qwe      (filter (function (x) (regexp:match x «^qwe...»)) -) ;   ,   ,    (filter-th (function (x) (regexp:match x «^qwe...»)) -) 

Implementierung


Der Interpreter ist in Delphi geschrieben (FreePascal im Kompatibilitätsmodus). Es wurde in Lazarus 1.6.2 und höher unter Windows und Linux 32 und 64 Bit erstellt. Für die externen Abhängigkeiten ist libmysql.dll erforderlich. Enthält ungefähr 15_000..20_000 Zeilen. Es gibt ungefähr 200 integrierte Funktionen für verschiedene Zwecke (einige sind achtmal überlastet).

Hier gespeichert

Die Unterstützung für die dynamische Typisierung erfolgt auf triviale Weise - alle verarbeiteten Datentypen werden von den Erben derselben TValue-Klasse dargestellt.

Der wichtigste Typ für Lisp - die Liste ist, wie in Delphi üblich, eine Klasse, die ein dynamisches Array von Objekten vom Typ TValue enthält. Für diesen Typ ist der CopyOnWrite-Mechanismus implementiert.

Die Speicherverwaltung erfolgt automatisch basierend auf der Referenzzählung. Bei rekursiven Strukturen werden alle Verknüpfungen in der Struktur gleichzeitig gezählt. Die Speicherfreigabe beginnt sofort, wenn die Variablen den Bereich verlassen. Es gibt keine Mechanismen für einen verzögerten Start des Garbage Collectors.

Die Ausnahmebehandlung funktioniert mit einem in Delphi integrierten Mechanismus. Somit können Fehler, die im Code des Interpreters auftreten, vom ausführbaren Code auf dem Fox verarbeitet werden.

Jeder Operator oder jede integrierte Lisi-Funktion ist als Methode oder Funktion im Interpretercode implementiert. Das Skript wird durch einen gegenseitig rekursiven Aufruf von Implementierungen ausgeführt. Der Interpretercode und das Skript haben einen gemeinsamen Aufrufstapel.

Skriptvariablen werden unabhängig voneinander im dynamischen Speicher gespeichert. Jede benutzerdefinierte Funktion verfügt über einen eigenen Stapel zum Speichern von Variablenreferenzen, unabhängig vom Stapel der obersten Ebene oder dem Stapel übergeordneter Funktionen.

Von besonderer Schwierigkeit ist die Implementierung des Zuweisungsoperators (Satzes) für Strukturelemente. Die direkte Berechnung des Zeigers auf das erforderliche Element führt zu der Gefahr von baumelnden Verknüpfungen, da die Lisi-Syntax das Ändern der Struktur während der Berechnung des erforderlichen Elements nicht verbietet. Als Kompromisslösung wird ein „Kettenzeiger“ implementiert - ein Objekt, das einen Verweis auf eine Variable und ein Array numerischer Indizes enthält, um den Pfad innerhalb der Struktur anzugeben. Ein solcher Zeiger ist auch anfällig für das Problem baumelnder Verbindungen, erzeugt jedoch im Falle eines Fehlers eine aussagekräftige Fehlermeldung.

Entwicklungswerkzeuge


1. Konsole

2. Texteditor
Ausgestattet mit Syntaxhervorhebung und der Möglichkeit, ein bearbeitbares Skript in F9 auszuführen.


Fazit


Im aktuellen Zustand löst das Projekt die Probleme, für die es konzipiert wurde, und erfordert keine weitere aktive Entwicklung. Viele der vorhandenen Mängel wirken sich nicht wesentlich auf die Arbeit aus.

Source: https://habr.com/ru/post/de424447/


All Articles