Hofstadters Anmerkungen zu Lisp (1983)
(gist.github.com/jackrusher)- Douglas Hofstadters Text von 1983 stellt Lisp, das in der KI-Forschung weit verbreitet war, als elegante und flexible Sprache vor, die auf einem kleinen mathematischen Kern aufbaut
- Die Interaktivität von Lisp zeigt sich in der read-eval-print loop: Der Benutzer gibt einen Ausdruck ein, der Interpreter liest ihn, wertet ihn aus, gibt das Ergebnis aus und wartet dann auf die nächste Eingabe
- Lisp-Objekte bestehen vor allem aus Atomen (atoms) und Listen (lists), wobei nil eine Sonderstellung einnimmt, da es sowohl die leere Liste als auch ein Atom ist
quote,eval,car,cdr,cons,setq,set,lambda,def,condbehandeln Code und Daten in derselben Listenstruktur und ermöglichen so, neue Funktionen schrittweise aufzubauen- Funktionskomposition, Bedingungsausdrücke, Seiteneffekte und applicative style sowie rekursives Denken werden anhand von Beispielen verknüpft; die abschließende Funktion
powerund die Metapher vom „porpuquine“ deuten auf Lisp-artige Rekursion hin
Die Stellung von Lisp in der KI-Forschung
- KI war 1983 ein Forschungsgebiet, das Verhaltensweisen wie Flexibilität, gesunden Menschenverstand, Einsicht, Kreativität, Selbstwahrnehmung und Humor in Computern umsetzen wollte
- In den USA gab es etwa 2.000 Personen, die professionell mit KI befasst waren, und im Ausland eine ähnliche Zahl von Forschern
- KI-Forscher waren sich über den besten Weg zur KI stark uneinig, verwendeten bei der Wahl der Programmiersprache aber fast durchgängig Lisp
- Der Name Lisp stammt von „list processing“ und ist kein vollständiges Akronym
- Die Attraktivität von Lisp liegt darin, dass es eine „crisp“ und „elegant“ wirkende Sprache ist
- Viele Sprachen enthalten zahlreiche willkürliche Features, während einige Sprachen wie Lisp und Algol um einen natürlichen Kern herum aufgebaut sind, fast wie ein Teilgebiet der Mathematik
- Der Kern von Lisp besitzt eine „crystalline purity“, und diese Reinheit trägt nicht nur zur Ästhetik, sondern auch zur Flexibilität bei
Von der mathematischen Logik zur Listenverarbeitungssprache
- Die tiefen Wurzeln von Lisp liegen in der mathematischen Logik
- Ideen, die Thoralf Skolem, Kurt Godel und Alonzo Church in den 1920er- und 1930er-Jahren zur Logik beitrugen, wurden später in Lisp aufgenommen
- Eigentliche Computerprogrammierung begann in den 1940er-Jahren, Hochsprachen wie Lisp kamen in den 1950ern auf
- Die erste Listenverarbeitungssprache war nicht Lisp, sondern IPL (Information Processing Language)
- IPL wurde Mitte der 1950er von Herbert Simon, Allen Newell und J. C. Shaw entwickelt
- John McCarthy entwickelte 1956–1958 auf Basis vorhandener Ideen Lisp als algebraische Listenverarbeitungssprache
- Lisp verbreitete sich schnell unter jungen Forschern rund um das MIT Artificial Intelligence Project
- Es wurde auf dem IBM 704 implementiert und breitete sich zu anderen KI-Gruppen aus
- Es entstanden viele Dialekte, die jedoch denselben eleganten Kern teilten
Interaktives Lisp und das REPL
- Im Unterschied zu vielen anderen Hochsprachen wurde Lisp als interaktive Sprache präsentiert
- Der Benutzer startet Lisp im Terminal, sieht einen Prompt und gibt einen Ausdruck ein
- Beispiel: Gibt man
(plus 2 2)ein, wird4ausgegeben und ein neuer Prompt erscheint
- Beispiel: Gibt man
- Das zentrale Verhalten des Lisp-Interpreters ist die read-eval-print loop
- Einen Ausdruck lesen
- Den Ausdruck auswerten
- Einen passenden Wert ausgeben
- Signalisieren, dass der nächste Ausdruck gelesen werden kann
- Durch diese Struktur kann der Benutzer seine „Wünsche“ einzeln eingeben und das Ergebnis sofort sehen
- In vielen nicht interaktiven Sprachen muss dagegen erst ein vollständiges Programm geschrieben und dann ausgeführt werden; dabei müssen viele Schritte perfekt ineinandergreifen, und Fehlerkorrektur ist indirekt und teuer
- Lisp ermöglicht durch die Ausführung eines Ausdrucks nach dem anderen schrittweise Entwicklung und Debugging
Polish notation und Klammern
- Arithmetische Ausdrücke in Lisp verwenden Polish notation, bei der der Operator vor den Operanden steht
- Beispiel:
(times (plus 6 3) (difference 6 3))wird zu 27 ausgewertet
- Beispiel:
- Diese Notation wurde schon vor dem Computerzeitalter vom polnischen Logiker Jan Lukasiewicz entwickelt
- In Lisp-Ausdrücken kommen viele Klammern vor
- Längere Ausdrücke enden häufig mit mehreren schließenden Klammern
- Das wirkt anfangs abschreckend, aber mit Gewöhnung wird die logische Struktur intuitiv sichtbar
- Die Beispiele werden in Form von pretty printing mit Einrückung dargestellt, um die Struktur zu zeigen
Atome, Listen und nil
- Das Herzstück von Lisp sind manipulierbare Strukturen; alle Programme funktionieren dadurch, dass sie Strukturen erzeugen, verändern und zerstören
- Es gibt zwei Arten von Strukturen
- Atome (atoms): grundlegende Objekte, die nicht weiter zerlegt werden können
- Listen (lists): Strukturen aus mehreren Elementen in einer bestimmten Reihenfolge
- Jedes Lisp-Objekt ist entweder ein Atom oder eine Liste; die einzige Ausnahme ist nil
- nil ist sowohl Atom als auch Liste
- nil steht für die leere Liste
()undnilbedeuten dasselbe, aber nil wird häufiger verwendet
- Elemente einer Liste können Atome oder Listen sein
- Beispiel:
(zonk blee strill (croak flonk))ist eine Liste mit vier Elementen, wobei das letzte Element wiederum eine Liste mit zwei Elementen ist
- Beispiel:
- Auch Lisp-Sätze selbst sind Listen
(plus 2 2)ist ebenfalls eine Liste- Der Lisp-Interpreter kann Listen und Atome manipulieren, um neue „Befehle“ zu bilden und auszuwerten
Werte, quote, eval
- Atome können Werte haben
- Zahlenatome haben dauerhaft sich selbst als Wert
- nil hat den Wert nil
- t ist ebenfalls ein spezielles Atom, das dauerhaft sich selbst als Wert hat
setqweist einem Atom einen Wert zu(setq pie 4)setzt den Wert vonpieauf 4(setq pie (plus 2 2))setzt den Wert vonpieebenfalls auf 4
- Der Wert eines Atoms muss nicht numerisch sein, sondern kann jedes Lisp-Objekt sein
- also ein Atom oder eine Liste
- quote verhindert die Auswertung und behandelt eine Liste oder ein Atom direkt als Daten
(setq pie (plus 2 2))wertet den inneren Ausdruck aus und speichert 4 inpie(setq pi '(plus 2 2))speichert die Liste(plus 2 2)selbst inpi
evalwertet einen als Wert gespeicherten Ausdruck erneut aus- Wenn der Wert von
pi(plus 2 2)ist, liefert(eval pi)den Wert 4 zurück - Der Interpreter wertet normalerweise nur eine Stufe aus, aber mit
evalkann man eine zusätzliche Auswertung anfordern
- Wenn der Wert von
car, cdr, cons
- Jede Liste außer nil hat mindestens ein Element; das erste Element heißt car
- Das car von
(eval pi)isteval - Das car von
(plus 2 2)istplus
- Das car von
- Das car muss nicht unbedingt ein Funktionsname sein
- Das car von
((1)(2 2) (3 3 3))ist(1)
- Das car von
- Entfernt man das car, erhält man den Rest der Liste, den cdr
- Der cdr von
(a b c d)ist(b c d) - Danach folgen
(c d),(d)und nil
- Der cdr von
- nil hat weder car noch cdr; der Versuch, car oder cdr davon zu nehmen, sollte einen Fehler auslösen
conssetzt vor eine bestehende Liste ein neues car und erzeugt so eine neue Liste- Wenn
xden Wert(cake cookie)hat, erzeugt(cons 'pie x)die Liste(pie cake cookie) - Das ursprüngliche
xwird dabei nicht verändert
- Wenn
- Das Ergebnis hängt davon ab, ob quote verwendet wird oder nicht
- Wenn
pieden Wert 4 hat, liefert(cons pie x)(4 cake cookie) (cons 'pie x)setzt dagegen den Atomnamenpieselbst an den Anfang
- Wenn
Der Unterschied zwischen setq und set
- Der Lisp-Interpreter wertet nicht immer alle Argumente aus;
setqist ein typisches Beispiel für eine Ausnahme - Das erste Argument von
setqwird nicht ausgewertet, sondern direkt als Variablenname behandelt- Das q in
setqsteht für quote; das erste Argument wird also behandelt, als wäre es quotiert
- Das q in
setähneltsetq, wertet aber auch das erste Argument aus- Wenn x den Wert k hat, ändert
(set x 7)nicht x, sondern setzt den Wert von k auf 7
- Wenn x den Wert k hat, ändert
- Als Beispiel wird folgende Folge gezeigt
(setq a 'b)(setq b 'c)(setq c 'a)(set a c)(set c b)
- Nach dieser Ausführung haben a, b und c jeweils alle den Wert
a setwird nicht oft verwendet, und solche Verwirrung kommt ebenfalls nicht häufig vor
Funktionsdefinition und lambda
- Eine große Stärke des Programmierens liegt darin, neue zusammengesetzte Operationen aus vorhandenen Operationen zu definieren und dies immer weiter zu wiederholen, sodass ein immer komplexeres Repertoire entsteht
- In Lisp definiert man neue Funktionen mit bereits bekannten Funktionen
racist ein Beispiel für eine Funktion, die das letzte Element einer Liste zurückgibt- Um das letzte Element zu erhalten, kann man die Liste umdrehen und dann car darauf anwenden
- Definition:
(def rac (lambda (lyst) (car (reverse lyst))))
(lyst)nachlambdabezeichnet den Parameter der Funktion, also eine Dummy-Variable- Nach der Definition mit
defkannracwiecarals Funktion verwendet werden(rac '(your brains))liefertbrainszurück
- Die Funktion
readers-digest-condensed-versionerzeugt aus einer langen Liste eine kurze Liste, die nur das erste und das letzte Element enthält- Wenn man James Joyces Finnegans Wake als Wortliste behandelt, erhält man als Beispiel
(riverrun the)
- Wenn man James Joyces Finnegans Wake als Wortliste behandelt, erhält man als Beispiel
- Eine Umkehrfunktion namens
rejoycewäre eine imaginäre Funktion, die einen Roman erzeugt, dessen Anfang und Ende durch zwei Wörter festgelegt sind; ihre Implementierung bleibt dem Leser als Übung überlassen
Rückgabewerte, Seiteneffekte, applicative style
- Manche sehen in Lisp und verwandten Sprachen zwei Ziele als wünschenswert und erreichbar an
- Jeder Satz liefert einen Wert zurück
- Die Wirkung eines Satzes entsteht ausschließlich über diesen Rückgabewert
- Die im Text behandelten Lisp-Dialekte erfüllen die erste Bedingung, aber nicht zwingend die zweite
(reverse x)verändert x selbst nicht, sondern erzeugt und liefert eine neue Liste zurück, die dieselben Elemente in umgekehrter Reihenfolge enthält- genauso wie
(plus 2 2)den Wert von 2 nicht verändert
- genauso wie
consverändert bestehende Listen ebenfalls nicht, sondern gibt eine neue Liste zurücksetqist dagegen ein Beispiel für einen Befehl mit Seiteneffekt, weil er Variablenbindungen verändert- Zu Seiteneffekten können neben Änderungen von Variablenbindungen auch Ein- und Ausgabe gehören
- Anhänger des applicative programming bevorzugen ausschließlich eine Art des Programmierens, bei der Funktionen Werte ohne Seiteneffekte berechnen und weitergeben
- Die einzige in diesem Stil erlaubte Bindung ist die vorübergehende lambda binding während eines Funktionsaufrufs
- Nach Abschluss der Berechnung verschwinden die Bindungen der Dummy-Variablen wieder
- Der Sprecher des Textes hält den applicative style für elegant, aber beim Bau großer Programme im Stil der KI nicht für praktikabel
- In streng applicative programming wäre sogar
defein extremer Grenzfall, weil es dauerhafte Funktionsdefinitionen im Speicher ablegt
Bedingungsausdruck cond und Entscheidung
- Damit Lisp interessantere Dinge tun kann, muss es abhängig davon, was im Zwischenprozess geschieht, Entscheidungen treffen können; dazu braucht es Bedingungsausdrücke
- Beispiel:
(cond ((eq x 1) 'land) ((eq x 2) 'sea))- Wenn x 1 ist, wird
landzurückgegeben - Wenn x 2 ist, wird
seazurückgegeben - Sonst wird nil zurückgegeben
- Wenn x 1 ist, wird
eqist eine Lisp-Funktion, dietzurückgibt, wenn die Werte ihrer beiden Argumente gleich sind, sonstnil- Ein
cond-Ausdruck beginnt mit dem Funktionsnamencondund enthält mehrere Klauseln- Jede Klausel ist eine Liste mit zwei Elementen: einer Bedingung und einem Ergebnis
- Die Bedingungen werden der Reihe nach geprüft; sobald die erste Bedingung einen Wert ungleich nil liefert, wird das zugehörige Ergebnis ausgewertet und als Wert des gesamten
condzurückgegeben - Die späteren Klauseln werden dann nicht mehr geprüft
- Fügt man am Ende eine catch-all-Klausel mit
tals Bedingung ein, kann man statt nil ein Standardergebnis zurückgeben- Beispielhaft wird ein
condgezeigt, dasairzurückgibt, wenn weder die Bedingung für land noch die für sea erfüllt ist
- Beispielhaft wird ein
Potenzen und rekursive Struktur
- Gegen Ende des Textes erscheinen Funktionsdefinitionen mit einem klaren Muster
squareistk * kcubeistk * square(k)4th-poweristk * cube(k)- und so weiter im selben Stil
- Es wird die Frage gestellt, ob sich dieses gesamte Muster durch eine einzige Funktion
powermit zwei Parametern erfassen lässt(power 9 3)soll 729 ergeben(power 7 4)soll 2.401 zurückgeben
- Alle nötigen Werkzeuge wurden im Text bereits vorgestellt; nun ist die Geistesgegenwart des Lesers gefragt
- Die abschließende Geschichte vom Glazunkian porpuquine behandelt rekursive Struktur in Form einer Parabel
- Die Stacheln eines porpuquine sind jeweils kleinere porpuquines
- In Outer Glazunkia hat jeder immer 9 Stacheln, in Inner Glazunkia immer 7
- Ein porpuquine von 0 Zoll hat keine Stacheln und beendet so den unendlichen Regress
- Die „buying power“ oder „power“ dieses Tiers steht mit der Zahl der darin enthaltenen kleinen Nasen von 0-Zoll-porpuquines in Verbindung und bildet die abschließende Metapher zum zuvor gestellten
power-Problem
Kontext der Wiederveröffentlichung des Originaltexts
- Im vorderen Teil des Gist wird erklärt, dass es sich bei diesem Text um Hofstadters Lisp-Einführung handelt, die der Autor Mitte der 1980er in einer älteren Ausgabe von Scientific American gefunden hatte
- Die Beispiele lassen sich in Emacs noch ausführen, wenn man einige Aliase installiert
plus→+quotient→/times→*difference→-
- Am Ende heißt es, wer diesen eigenwilligen Lisp-Leitfaden von Hofstadter mochte, finde in seinem Buch Metamagical Themas weitere ähnliche Texte
1 Kommentare
Hacker-News-Kommentare
Die Funktionsnamen
"oval"und"snot"im Beispiel haben mich verwirrt, aber nach ein paar Sekunden wurde mir klar, dass es Tippfehler für"eval"bzw."snoc"sind.Der ursprüngliche Code sollte sich so lesen:
(cond ((eq (eval pi) pie) (eval (snoc pie pi))) (t (eval (snoc (rac pi) pi))))Ich habe mir den originalen Scientific-American-Artikel unter https://www.jstor.org/stable/24968822 geholt und nachgesehen: Tatsächlich waren
oval/snotTippfehler füreval/snoc."oval"und"snot"sind tatsächlich Tippfehler für"eval"und"snoc".Und
snocscheint ein umgedrehtesconszu sein,racein umgedrehtescar.Dieser Text zeigt erneut, dass ein Hauptgrund für die geringe Popularität von Lisp die Art war, wie Lisp erklärt wurde.
Solche Lisp-Texte haben mir als Kind nichts gezeigt, was ich tatsächlich hätte benutzen können, und auch nicht, wie sehr eine Aufgabe X im Vergleich zu Assembler/C/Pascal usw. leichter würde.
Ich heute hätte gezeigt: „Eine Rechtschreibprüfung, die in Assembler sieben Monate dauert? Auf einem Mikrocomputer mit Bank-Switching-Speicher ist das in Lisp fast trivial, und selbst auf einer miserablen CPU lässt sich Garbage Collection deterministisch machen.“
Zahllose Lisp-Artikel und Lehrbücher wiederholten nur Listen, Rekursion und KI, ohne zu zeigen, wie man nützliche Dinge erledigt; dadurch fühlt sich die Zeit verschwendet an, als hätte man mit Pinzette, Reiskörnern und Klebstoff programmiert.
In der ersten Byte-Ausgabe, die ich als Kind sah, gab es Code für symbolische Differentiation und algebraische Vereinfachung in Lisp; er war schwer nachzuvollziehen, aber ich spürte, dass daran etwas Faszinierendes war.
In Basic wäre es sicher nicht leichter gewesen, und später merkte ich, dass der Code nicht gerade hervorragend war; aber erst als ich Ende der 80er auf dem PC XLisp und SICP kennenlernte, verfiel ich Lisp so richtig.
Ich mag Hofstadters Schreibstil wirklich sehr. Er fängt gut ein, was ich empfand, als ich Lisp entdeckte.
Ich war ein Kind, das in den 80ern Programmieren lernte, und hatte in der Highschool und zu Beginn des Studiums schon ein wenig BASIC, Fortran, Pascal und COBOL gemacht; trotz Unterschieden hatten sie im Kern etwas Gemeinsames.
Doch der erste Informatikkurs an der UC Berkeley wurde in Scheme, einem Lisp-Dialekt, unterrichtet, und das war ein völliger Schock.
Wie Hofstadter sagt, fühlte es sich der Mathematik am nächsten an, erinnerte mich stark an Kurse in mathematischer Theorie und war die erste schöne Sprache, die ich entdeckte.
Besonders gefiel mir das Zitat: „Lisp und Algol sind um Kerne herum gebaut, die so natürlich sind wie ein Zweig der Mathematik. Im Kern von Lisp liegt eine kristalline Reinheit, die nicht nur den Sinn für Ästhetik anspricht, sondern es auch viel flexibler macht als die meisten Sprachen.“
Selbst aus der Perspektive von jemandem, der algebraischer Topologe werden wollte, ist Scheme eher eine elegante und minimale Darstellung eines Programmierstils, der in Sprachen mit dynamischer Typisierung und Garbage Collection allgemein verbreitet ist.
Es wirkt vollständig und in dem Sinne wie „Theorie“, dass man in Scheme über eine Problemlösung nachdenken und sie anschließend in eine andere dynamisch typisierte Sprache übertragen kann, um zu einer eleganten Lösung zu gelangen.
Scheme war im Vergleich zum traditionellen Lisp seiner Zeit eine vereinfachte und aufgeräumte Sprache, etwa durch lexikalischen Scope und einen einheitlichen Namensraum für Funktionen und Variablen.
1983 lag der Beginn dieses Felds etwa 20 Jahre zurück, und hier ist von KI die Rede, die Computer so programmieren soll, dass sie „Flexibilität, gesunden Menschenverstand, Einsicht, Kreativität, Selbstbewusstsein und Humor“ besitzen.
Es ist ziemlich amüsant, weil sich diese Liste wie eine Liste der Dinge liest, in denen LLMs wirklich schlecht sind.
Trotzdem hat es in diese Richtung zumindest mehr als null Fortschritt gegeben.
Vielleicht empfinde ich sogar, dass sie mehr davon zeigen, aber wenn ich das sage, klingt es selbst für mich etwas merkwürdig.
Auch wenn vieles davon noch nicht zu Ergebnissen geführt hat, ist es interessant zu lesen.
Die beiden Begleitartikel, die zusammen mit diesem Text erwähnt wurden, stehen in Hofstadters Buch Metamagical Themas in den Kapiteln 17 bis 19; dort finden sich auch weitere Texte aus derselben Kolumne im Scientific American.
[0]: https://www.goodreads.com/book/show/181239.Metamagical_Thema...
Der Buchtitel stammt vom Titel der Kolumne, einem Anagramm von Martin Gardners „Mathematical Games“ im Scientific American; Hofstadter übernahm diese Kolumne.
Im Artikel heißt es, dass das Anwenden von
carodercdraufnileinen Fehler wie eine Division durch 0 auslösen sollte; in modernen Lisp-Dialekten ist das aber oft nicht mehr der Fall.In dem ursprünglichen Lisp, das John McCarthy definierte, waren
CARundCDRfürNILnicht definiert: <https://dl.acm.org/doi/pdf/10.1145/367177.367199>Common Lisp und Emacs Lisp definieren
(car nil)und(cdr nil)jedoch alsnil: <https://www.lispworks.com/documentation/HyperSpec/Body/f_car...>, <https://www.gnu.org/software/emacs/manual/html_node/elisp/Li...>In
LISP 2156gab(status lispversion)/2156zurück, und(car nil)sowie(cdr nil)lieferten beideNIL.carundcdrals zentrale Elemente eingegangen sind.Die grundlegendsten und bekanntesten Operatoren des „List Processor“ waren nicht für Listen gemacht, sondern für cons, ein bestimmtes Element der Maschinenrepräsentation, das Lisp zum Aufbau von Datenstrukturen verwendet.
Ein cons wird auch nicht immer als Liste interpretiert, und die äußerst wichtige Liste, die den Basisfall rekursiver Listenfunktionen bildet, wird nicht einmal als cons dargestellt.
Auch 60 Jahre später sind die meisten Lisp-Programme voller cons-Operationen; der genauere Name wäre vielleicht „Cons Processor“ gewesen.
Das erinnert daran, dass Lisp in einer Zeit entstand, in der Sprache und Implementierung eng ineinandergreifen mussten, und dadurch wirkt die Leistung, eine Computersprache auf mathematischer Logik zu gründen, noch erstaunlicher.
CARoderCDRvonnilnimmt, ist das ein Fehler.car/cdrvonnilin Scheme ein Fehler, sodass sich der Code für Anfänger wie mich ziemlich unergonomisch anfühlt.Trotzdem bevorzuge ich Guile Scheme gegenüber Common Lisp, was es umso bedauerlicher macht.
nilohne Fehler wiedernilzurückgibt.SQL kommt mir ebenfalls in den Sinn, und weil ich das persönlich für keine gute Entscheidung halte, würde ich es, falls es keinen anderen Begriff gibt, gern „bleeding nils/NULLs“ nennen.
Besonders schlecht ist es, wenn
nilin booleschen Vergleichen nicht dasselbe ist wiefalse.In Ruby und Elixir wird
nilwiefalsebehandelt; Elixir bietet getrenntand, das nur reine Boolesche Werte akzeptiert, und&&, dasnilalsfalsebehandelt.Ein solches Design kann Code anfangs sauberer machen, aber ein falsch behandeltes
nil, das eigentlich ein Fehler ist, kann erst viele Call-Stack-Ebenen später an einer ganz anderen Stelle sichtbar werden und das Debugging deutlich erschweren.Die Lisp-Informationen in diesem Artikel kannte ich schon, aber ich habe ihn trotzdem mit Vergnügen gelesen. Hofstadters Sprachwitz ist wirklich reizvoll.
Besonders lustig fand ich den Scherz, die inverse Operation zu
readers-digest-condensed-versionnamensrejoycezu erfinden, sodass beim Ausführen von(rejoyce 'Stately 'Yes)eine Lisp-Fee den gesamten Ulysses, wie James Joyce ihn hätte schreiben können, von Anfang an erzeugt.Es hat eine Weile gedauert, aber letztlich sind wir wohl an diesem Punkt angekommen; auch wenn die KI des Jahres 2024 nicht völlig dem entspricht, was er sich 1983 vorgestellt hat, passt das Reproduzieren von Text aus einem kurzen Seed heute ziemlich gut zu KI.
Lisp ist meiner Ansicht nach nach wie vor eine der wichtigsten Sprachen, mit denen sich Hofstadters Konzept der seltsamen Schleifen ausdrücken lässt.
Lisp ist nicht die einzige homoikonische Sprache, gehört aber unter den Sprachen, die Menschen tatsächlich beherrschen, zu den größten, bei denen
evalkeinen zu parsenden String entgegennimmt.Ich mag es nicht, wenn Leute Lisp kurzerhand mit funktionaler Programmierung insgesamt gleichsetzen.
Ich habe nichts gegen funktionale Programmierung, aber der symbolische Charakter von Lisp ist viel interessanter; und dass man Code schreiben kann, der über
(go tag)-Abschnitte läuft, wodurch auch GOTO-artige Programmierung sehr einfach möglich ist, finde ich unendlich faszinierend.Metaprogrammierung, bei der man mit XSLT und XML XSLT erzeugte, statt repetitiven Code direkt zu schreiben, wurde recht breit genutzt.
Allerdings war die Syntax ein noch größeres Problem als bei Lisp.
Es ist nicht leicht, eine Sprache zu schaffen, deren Syntax sich im Alltag gut verwenden lässt und die zugleich als abstrakter Syntaxbaum nicht zu schwer handhabbar ist; Lisp ist eines der wenigen Beispiele, bei denen das vergleichsweise gut gelungen ist.
functoolsnicht gut abbilden lässt: https://docs.python.org/3/library/functools.htmlIch baue eine symbolische AGI ohne Lisp und würde gern Hinweise von Experten hören.
So wie ich die Python-Funktionen verstehe, sind das Funktionen wie
filter(),map()undreduce(), die andere Funktionen auf iterierbare Objekte anwenden; Wrapper wie@singledispatch, die Aufrufe routen; Funktionen wie@cacheundpartial(), die bei Ablaufsteuerung oder Performance helfen; sowie Möglichkeiten wiewraps(), Funktionen beliebig zu umhüllen.Das wirkt größtenteils wie Komfortfunktionen, um Funktionen auf ungewöhnliche Weise aufzurufen, und scheint nicht an das Niveau der Lisp-Lobpreisungen heranzureichen, die „Selbstreflexion“ als Anliegen erster Klasse behandeln.
Ich möchte wissen, was Lisp tatsächlich bietet, das in den oben genannten Funktionen fehlt.
Sie verwenden Lisp als Mittel zur Vermittlung von Konzepten, und diese Konzepte drehen sich meist um funktionale Rekursion.
Auch Scheme und die Kultur darum herum tragen dazu bei.
Scheme ist zwar nicht nur eine funktionale Sprache, betont aber stärker als frühere Lisp-Dialekte reine Programmierung; die Basissprache stellt statt Iterationskonstrukten Strukturen für Tail Recursion bereit und verlangt von Implementierungen Tail-Call-Optimierung.
Auch in Common Lisp kann man
defaliasals Makro definieren und es wie Emacs’defaliasverwenden: https://stackoverflow.com/questions/24252539/defining-aliase...Ich mochte Hofstadters Lisp-Artikel in Metamagical Themas und habe den Code aus dem letzten Beitrag dieser Reihe für eine Studiengruppe bei der Arbeit nach Clojure übertragen und aufbereitet.
[1] http://johnj.com/posts/oodles/