Loading...

PRÄGNANZ IST MACHT

Original

Mai 2002

„Die durch algebraische Zeichen in einen kleinen Raum komprimierte Bedeutungsmenge ist ein weiterer Umstand, der die Schlussfolgerungen erleichtert, die wir normalerweise mit ihrer Hilfe anstellen.“

  • Charles Babbage, zitiert in Iversons Turing Award Lecture

In der Diskussion über die von Revenge of the Nerds auf der LL1-Mailingliste aufgeworfenen Fragen schrieb Paul Prescod etwas, das mir im Gedächtnis blieb.

Das Ziel von Python ist Regelmäßigkeit und Lesbarkeit, nicht Prägnanz.

Auf den ersten Blick scheint dies eine ziemlich vernichtende Aussage über eine Programmiersprache zu sein. Soweit ich das beurteilen kann, ist Prägnanz = Macht. Wenn das so ist, dann erhalten wir durch Ersetzen

Das Ziel von Python ist Regelmäßigkeit und Lesbarkeit, nicht Leistung.

und das scheint kein Kompromiss zu sein (falls es überhaupt ein Kompromiss ist ), den Sie eingehen möchten. Es ist nicht weit davon entfernt zu sagen, dass Pythons Ziel nicht darin besteht, als Programmiersprache effektiv zu sein.

Ist Prägnanz = Macht? Das scheint mir eine wichtige Frage zu sein, vielleicht die wichtigste Frage für jeden, der sich für Sprachdesign interessiert, und eine, der man sich direkt stellen sollte. Ich bin mir noch nicht sicher, ob die Antwort ein einfaches Ja ist, aber es scheint mir eine gute Hypothese für den Anfang zu sein.

Hypothese

Meine Hypothese ist, dass Prägnanz Macht ist oder nahe genug beieinander liegt, um sie, außer in pathologischen Beispielen, als identisch behandeln zu können.

Meiner Meinung nach ist Prägnanz das Ziel von Programmiersprachen. Computern würde es genauso gut gefallen, wenn man ihnen direkt in Maschinensprache sagen würde, was sie tun sollen. Ich denke, der Hauptgrund, warum wir uns die Mühe machen, Hochsprachen zu entwickeln, ist der, dass wir uns einen Vorteil verschaffen wollen, sodass wir in 10 Zeilen einer Hochsprache sagen (und, was noch wichtiger ist, denken) können, was 1000 Zeilen Maschinensprache erfordern würde. Mit anderen Worten: Der Hauptzweck von Hochsprachen besteht darin, den Quellcode kleiner zu machen.

Wenn der Zweck höherer Programmiersprachen darin besteht, den Quellcode zu verkleinern, und die Leistungsfähigkeit einer Sache darin besteht, wie gut sie ihren Zweck erfüllt, dann liegt die Leistungsfähigkeit einer Programmiersprache darin, wie klein sie Ihre Programme macht.

Umgekehrt erfüllt eine Sprache, die Ihre Programme nicht klein macht, nicht die Aufgabe von Programmiersprachen – wie ein Messer, das nicht gut schneidet, oder eine unleserliche Schrift.

Metriken

Aber in welchem Sinne ist klein? Das gängigste Maß für die Codegröße sind Codezeilen. Aber ich denke, dass dieses Maß am gebräuchlichsten ist, weil es am einfachsten zu messen ist. Ich glaube nicht, dass irgendjemand wirklich glaubt, dass es der wahre Maßstab für die Länge eines Programms ist. Verschiedene Sprachen haben unterschiedliche Konventionen dafür, wie viel Code man in eine Zeile schreiben sollte; in C haben viele Zeilen nichts weiter als ein oder zwei Trennzeichen.

Ein weiterer einfacher Test ist die Anzahl der Zeichen in einem Programm, aber auch dieser ist nicht besonders gut; einige Sprachen (beispielsweise Perl) verwenden einfach kürzere Bezeichner als andere.

Ich denke, ein besseres Maß für die Größe eines Programms wäre die Anzahl der Elemente, wobei ein Element alles ist, was ein eindeutiger Knoten wäre, wenn Sie einen Baum zeichnen würden, der den Quellcode darstellt. Der Name einer Variablen oder Funktion ist ein Element; eine Ganzzahl oder eine Gleitkommazahl ist ein Element; ein Segment eines wörtlichen Textes ist ein Element; ein Element eines Musters oder einer Formatanweisung ist ein Element; ein neuer Block ist ein Element. Es gibt Grenzfälle (ist -5 zwei Elemente oder eins?), aber ich denke, die meisten davon sind für jede Sprache gleich, sodass sie Vergleiche nicht stark beeinflussen.

Diese Metrik muss noch ausgearbeitet werden und könnte im Fall bestimmter Sprachen eine Interpretation erfordern, aber ich denke, sie versucht, das Richtige zu messen, nämlich die Anzahl der Teile, aus denen ein Programm besteht. Ich denke, der Baum, den Sie in dieser Übung zeichnen würden, ist das, was Sie in Ihrem Kopf erstellen müssen, um sich das Programm vorzustellen, und daher ist seine Größe proportional zu der Menge an Arbeit, die Sie aufwenden müssen, um ihn zu schreiben oder zu lesen.

Design

Mit dieser Art von Metrik könnten wir verschiedene Sprachen vergleichen, aber das ist, zumindest für mich, nicht ihr Hauptnutzen. Der Hauptnutzen des Prägnanztests besteht darin, als Leitfaden für die Entwicklung von Sprachen zu dienen. Der nützlichste Vergleich zwischen Sprachen ist der zwischen zwei potenziellen Varianten derselben Sprache. Was kann ich in der Sprache tun, um Programme kürzer zu machen?

Wenn die konzeptionelle Belastung eines Programms proportional zu seiner Komplexität ist und ein bestimmter Programmierer eine feste konzeptionelle Belastung tolerieren kann, dann ist das dasselbe wie die Frage: „Was kann ich tun, damit Programmierer das Beste erreichen?“ Und das scheint mir dasselbe zu sein wie die Frage: „Wie kann ich eine gute Sprache entwickeln?“

(Übrigens wird durch nichts offensichtlicher, dass die alte Leier „alle Sprachen sind gleichwertig“ falsch ist, als durch das Entwerfen von Sprachen. Wenn Sie eine neue Sprache entwerfen, vergleichen Sie ständig zwei Sprachen – die Sprache, wenn ich x getan hätte, und die Sprache, wenn ich x nicht getan hätte –, um zu entscheiden, welche besser ist. Wenn dies wirklich eine bedeutungslose Frage wäre, könnten Sie genauso gut eine Münze werfen.)

Das Streben nach Prägnanz scheint eine gute Möglichkeit zu sein, neue Ideen zu finden. Wenn Sie etwas tun können, das viele verschiedene Programme kürzer macht, ist das wahrscheinlich kein Zufall: Sie haben wahrscheinlich eine nützliche neue Abstraktion entdeckt. Vielleicht können Sie sogar ein Programm schreiben, das Ihnen hilft, indem Sie den Quellcode nach wiederholten Mustern durchsuchen. Unter den anderen Sprachen sind diejenigen, die für ihre Prägnanz bekannt sind, diejenigen, die Sie für neue Ideen heranziehen sollten: Forth, Joy, Icon.

Vergleich

Der erste, der über diese Probleme schrieb, war meines Wissens Fred Brooks im Mythical Man Month . Er schrieb, dass Programmierer unabhängig von der Sprache anscheinend ungefähr die gleiche Menge Code pro Tag generieren. Als ich das mit Anfang zwanzig zum ersten Mal las, war das für mich eine große Überraschung und schien enorme Auswirkungen zu haben. Es bedeutete, dass (a) die einzige Möglichkeit, Software schneller zu schreiben, darin bestand, eine prägnantere Sprache zu verwenden, und (b) jemand, der sich die Mühe machte, dies zu tun, Konkurrenten, die dies nicht taten, in den Schatten stellen konnte.

Brooks Hypothese scheint, wenn sie stimmt, den Kern des Hackens zu treffen. In den darauffolgenden Jahren habe ich alle Beweise, die ich zu dieser Frage finden konnte, aufmerksam verfolgt, von formalen Studien bis hin zu Anekdoten über einzelne Projekte. Ich habe nichts gesehen, was seiner Hypothese widerspricht.

Ich habe noch keine Beweise gesehen, die mir schlüssig erschienen, und ich erwarte auch keine. Studien wie Lutz Prechelts Vergleich von Programmiersprachen liefern zwar die von mir erwarteten Ergebnisse, neigen aber dazu, Probleme zu verwenden, die zu kurz sind, um aussagekräftige Tests zu sein. Ein besserer Test einer Sprache ist, was in Programmen passiert, deren Schreiben einen Monat dauert. Und der einzige echte Test – wenn Sie wie ich glauben, dass der Hauptzweck einer Sprache darin besteht, gut zum Denken zu sein (und nicht nur einem Computer zu sagen, was er tun soll, wenn Sie daran gedacht haben) – ist, was Sie Neues darin schreiben können. Jeder Sprachvergleich, bei dem Sie eine vordefinierte Spezifikation erfüllen müssen, testet also leicht das Falsche.

Der wahre Test einer Sprache besteht darin, wie gut Sie neue Probleme entdecken und lösen können, nicht darin, wie gut Sie sie verwenden können, um ein Problem zu lösen, das jemand anderes bereits formuliert hat. Dies sind zwei völlig unterschiedliche Kriterien. In der Kunst funktionieren Medien wie Stickerei und Mosaik gut, wenn Sie im Voraus wissen, was Sie machen möchten, aber sie sind absolut miserabel, wenn Sie das nicht wissen. Wenn Sie das Bild entdecken möchten, während Sie es machen - wie Sie es beispielsweise bei so komplexen Dingen wie dem Bild einer Person tun müssen -, müssen Sie ein flüssigeres Medium wie Bleistift, Tusche oder Ölfarbe verwenden. Und tatsächlich werden Wandteppiche und Mosaike in der Praxis so hergestellt, dass man zuerst ein Gemälde anfertigt und es dann kopiert. (Das Wort „Cartoon“ wurde ursprünglich verwendet, um ein Gemälde zu beschreiben, das für diesen Zweck bestimmt war.)

Das bedeutet, dass wir wahrscheinlich nie einen genauen Vergleich der relativen Leistungsfähigkeit von Programmiersprachen haben werden. Wir werden zwar präzise Vergleiche haben, aber keine genauen. Insbesondere explizite Studien zum Vergleich von Sprachen werden dazu neigen, die Leistungsfähigkeit der leistungsstärkeren Sprachen zu unterschätzen, da sie wahrscheinlich kleine Probleme und notwendigerweise vordefinierte Probleme verwenden werden.

Berichte aus der Praxis sind zwar zwangsläufig weniger präzise als „wissenschaftliche“ Studien, aber wahrscheinlich aussagekräftiger. So kam Ulf Wiger von Ericsson in einer Studie zu dem Schluss, dass Erlang 4-10x prägnanter als C++ und die Softwareentwicklung proportional schneller sei in:

Vergleiche zwischen Ericsson-internen Entwicklungsprojekten zeigen eine ähnliche Produktivität pro Zeile/Stunde, einschließlich aller Phasen der Softwareentwicklung, ziemlich unabhängig davon, welche Sprache (Erlang, PLEX, C, C++ oder Java) verwendet wurde. Was die verschiedenen Sprachen dann unterscheidet, ist das Quellcodevolumen.

Die Studie beschäftigt sich auch explizit mit einem Punkt, der in Brooks' Buch nur implizit enthalten war (da er die Zeilen von debuggtem Code gezählt hat): Programme, die in leistungsfähigeren Sprachen geschrieben sind, haben tendenziell weniger Fehler. Dies wird zu einem Selbstzweck, der bei Anwendungen wie Netzwerk-Switches möglicherweise wichtiger ist als die Produktivität des Programmierers.

Der Geschmackstest

Letztendlich denke ich, dass Sie Ihrem Bauchgefühl folgen müssen. Wie fühlt es sich an, in dieser Sprache zu programmieren? Ich denke, der Weg, die beste Sprache zu finden (oder zu entwickeln), besteht darin, ein Gespür dafür zu entwickeln, wie gut eine Sprache Ihnen das Denken ermöglicht, und dann die Sprache auszuwählen/zu entwickeln, die sich am besten anfühlt. Wenn eine Sprachfunktion unangenehm oder einschränkend ist, machen Sie sich keine Sorgen, Sie werden es merken.

Eine solche Überempfindlichkeit hat ihren Preis. Sie werden feststellen, dass Sie das Programmieren in schwerfälligen Sprachen nicht ertragen können. Ich empfinde es als unerträglich einschränkend, in Sprachen ohne Makros zu programmieren, genauso wie jemand, der an dynamische Typisierung gewöhnt ist, es als unerträglich einschränkend empfindet, wieder in einer Sprache programmieren zu müssen, in der man den Typ jeder Variablen deklarieren muss und keine Liste von Objekten unterschiedlichen Typs erstellen kann.

Ich bin nicht der Einzige. Ich kenne viele Lisp-Hacker, denen das passiert ist. Tatsächlich ist der genaueste Maßstab für die relative Leistungsfähigkeit von Programmiersprachen wahrscheinlich der Prozentsatz der Leute, die die Sprache beherrschen und jeden Job annehmen, bei dem sie diese Sprache verwenden können, unabhängig vom Anwendungsbereich.

Restriktion

Ich denke, die meisten Hacker wissen, was es bedeutet, wenn eine Sprache einengend wirkt. Was passiert, wenn man das spürt? Ich glaube, es ist dasselbe Gefühl, das man hat, wenn die Straße, die man nehmen möchte, gesperrt ist und man einen langen Umweg machen muss, um ans Ziel zu kommen. Man möchte etwas sagen, aber die Sprache lässt es nicht zu.

Was hier wirklich vor sich geht, ist meiner Meinung nach, dass eine restriktive Sprache eine ist, die nicht prägnant genug ist. Das Problem ist nicht einfach, dass Sie nicht sagen können, was Sie eigentlich sagen wollten. Es ist, dass der Umweg, den die Sprache Ihnen auferlegt, länger ist. Versuchen Sie dieses Gedankenexperiment. Angenommen, Sie möchten ein Programm schreiben, und die Sprache lässt Sie es nicht so ausdrücken, wie Sie es möchten, sondern zwingt Sie, das Programm auf eine andere, kürzere Weise zu schreiben. Zumindest für mich würde sich das nicht sehr restriktiv anfühlen. Es wäre, als ob die Straße, die Sie nehmen möchten, gesperrt wäre und der Polizist an der Kreuzung Sie zu einer Abkürzung statt zu einem Umweg führen würde. Großartig!

Ich denke, dass das Gefühl der Einschränkung größtenteils (neunzig Prozent?) daher kommt, dass man gezwungen ist, das Programm, das man in der Sprache schreibt, länger zu machen, als man es im Kopf hat. Einschränkung ist hauptsächlich ein Mangel an Prägnanz. Wenn sich eine Sprache also restriktiv anfühlt, bedeutet das (hauptsächlich), dass sie nicht prägnant genug ist, und wenn eine Sprache nicht prägnant ist, wird sie sich restriktiv anfühlen.

Lesbarkeit

Das Zitat, mit dem ich begonnen habe, erwähnt zwei weitere Eigenschaften: Regelmäßigkeit und Lesbarkeit. Ich bin mir nicht sicher, was Regelmäßigkeit ist oder welchen Vorteil, wenn überhaupt, regelmäßiger und lesbarer Code gegenüber bloß lesbarem Code hat. Aber ich glaube, ich weiß, was mit Lesbarkeit gemeint ist, und ich glaube, es hat auch etwas mit Prägnanz zu tun.

Wir müssen hier sorgfältig zwischen der Lesbarkeit einer einzelnen Codezeile und der Lesbarkeit des gesamten Programms unterscheiden. Es kommt auf die zweite Zeile an. Ich stimme zu, dass eine Zeile in Basic wahrscheinlich besser lesbar ist als eine Zeile in Lisp. Aber ein in Basic geschriebenes Programm wird mehr Zeilen haben als dasselbe Programm in Lisp (insbesondere, wenn man ins Greenspunland wechselt). Der Gesamtaufwand zum Lesen des Basic-Programms wird sicherlich größer sein.

Gesamtaufwand = Aufwand pro Zeile x Zeilenanzahl

Ich bin mir nicht so sicher, dass die Lesbarkeit direkt proportional zur Prägnanz ist, wie ich mir sicher bin, dass die Aussagekraft es ist, aber sicherlich ist Prägnanz ein Faktor (im mathematischen Sinne; siehe Gleichung oben) der Lesbarkeit. Daher ist es vielleicht nicht einmal sinnvoll zu sagen, dass das Ziel einer Sprache die Lesbarkeit und nicht die Prägnanz ist; es könnte so sein, als würde man sagen, das Ziel sei die Lesbarkeit und nicht die Lesbarkeit.

Für den Benutzer, der zum ersten Mal mit der Sprache in Berührung kommt, bedeutet Lesbarkeit pro Zeile, dass der Quellcode nicht bedrohlich aussieht . Lesbarkeit pro Zeile könnte also eine gute Marketingentscheidung sein, selbst wenn es eine schlechte Designentscheidung ist. Es ist isomorph zu der sehr erfolgreichen Technik, die Leute in Raten zahlen zu lassen: Anstatt sie mit einem hohen Vorabpreis zu erschrecken, teilt man ihnen die niedrige monatliche Rate mit. Ratenzahlungspläne sind jedoch ein Nettoverlust für den Käufer, wie es die bloße Lesbarkeit pro Zeile wahrscheinlich für den Programmierer ist. Der Käufer wird viele dieser sehr niedrigen Raten zahlen; und der Programmierer wird viele dieser einzeln lesbaren Zeilen lesen.

Dieser Kompromiss existiert schon vor der Zeit der Programmiersprachen. Wenn Sie es gewohnt sind, Romane und Zeitungsartikel zu lesen, kann Ihre erste Erfahrung mit der Lektüre einer mathematischen Arbeit entmutigend sein. Es kann eine halbe Stunde dauern, eine einzige Seite zu lesen. Und dennoch bin ich ziemlich sicher, dass die Notation nicht das Problem ist, auch wenn es sich so anfühlen mag. Die mathematische Arbeit ist schwer zu lesen, weil die Ideen schwer sind. Wenn Sie dieselben Ideen in Prosa ausdrücken würden (wie es Mathematiker tun mussten, bevor sie prägnante Notationen entwickelten), wären sie nicht leichter zu lesen, weil die Arbeit die Größe eines Buches annehmen würde.

Inwieweit?

Viele Leute haben die Idee abgelehnt, dass Prägnanz = Macht ist. Ich denke, es wäre sinnvoller, statt einfach zu argumentieren, dass die beiden gleich sind oder nicht, zu fragen: Inwieweit ist Prägnanz = Macht? Denn Prägnanz ist ganz klar ein großer Teil dessen, wofür höhere Sprachen da sind. Wenn das nicht ihr einziger Zweck ist, wofür sind sie dann sonst da und wie wichtig sind diese anderen Funktionen im Verhältnis dazu?

Ich schlage das nicht nur vor, um die Debatte zivilisierter zu gestalten. Ich möchte die Antwort wirklich wissen. Wann, wenn überhaupt, ist eine Sprache zu prägnant für ihr eigenes Wohl?

Meine Ausgangshypothese war, dass Prägnanz, außer in pathologischen Beispielen, als identisch mit Macht angesehen werden könnte. Was ich meinte war, dass sie in jeder Sprache, die irgendjemand entwerfen würde, identisch wären, aber dass, wenn jemand eine Sprache entwerfen wollte, die diese Hypothese ausdrücklich widerlegt, er das wahrscheinlich tun könnte. Tatsächlich bin ich mir da nicht einmal sicher.

Sprachen, nicht Programme

Wir sollten uns darüber im Klaren sein, dass wir über die Prägnanz von Sprachen sprechen, nicht von einzelnen Programmen. Es ist durchaus möglich, dass einzelne Programme zu dicht geschrieben sind.

Ich habe darüber in On Lisp geschrieben. Ein komplexes Makro muss möglicherweise ein Vielfaches seiner eigenen Länge einsparen, um gerechtfertigt zu sein. Wenn Sie durch das Schreiben eines komplizierten Makros bei jeder Verwendung zehn Zeilen Code sparen könnten und das Makro selbst zehn Zeilen Code umfasst, dann sparen Sie netto Zeilen, wenn Sie es mehr als einmal verwenden. Aber das könnte immer noch ein schlechter Schachzug sein, da Makrodefinitionen schwerer zu lesen sind als normaler Code. Sie müssen das Makro möglicherweise zehn oder zwanzig Mal verwenden, bevor es eine Nettoverbesserung der Lesbarkeit bringt.

Ich bin sicher, dass jede Sprache solche Kompromisse hat (obwohl ich vermute, dass die Risiken umso größer werden, je leistungsfähiger die Sprache wird). Jeder Programmierer hat bestimmt schon einmal Code gesehen, den ein kluger Mensch mithilfe fragwürdiger Programmiertricks geringfügig verkürzt hat.

Daran gibt es also keinen Widerspruch – zumindest nicht von mir. Einzelne Programme können durchaus zu prägnant für ihr eigenes Wohl sein. Die Frage ist, kann eine Sprache das sein? Kann eine Sprache Programmierer dazu zwingen, Code zu schreiben, der (in Elementen) kurz ist, auf Kosten der allgemeinen Lesbarkeit?

Ein Grund, warum es schwer ist, sich eine Sprache als zu prägnant vorzustellen, ist, dass es, wenn es eine übermäßig kompakte Möglichkeit gäbe, etwas auszudrücken, wahrscheinlich auch eine längere Möglichkeit gäbe. Wenn Sie beispielsweise der Meinung sind, dass Lisp-Programme mit vielen Makros oder höherwertigen Funktionen zu dicht sind, könnten Sie, wenn Sie möchten, Code schreiben, der zu Pascal isomorph ist. Wenn Sie die Fakultät in Arc nicht als Aufruf einer höherwertigen Funktion ausdrücken möchten

 (rec zero 1 * 1-)

Sie können auch eine rekursive Definition ausschreiben:

 (rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))

Obwohl mir spontan keine Beispiele einfallen, interessiert mich die Frage, ob eine Sprache zu prägnant sein kann. Gibt es Sprachen, die einen zwingen, Code auf eine Art und Weise zu schreiben, die launisch und unverständlich ist? Wenn jemand Beispiele hat, würde ich sie sehr gerne sehen.

(Zur Erinnerung: Ich suche nach Programmen, die gemäß der oben skizzierten Metrik der „Elemente“ sehr dicht sind, und nicht nur nach Programmen, die kurz sind, weil Trennzeichen weggelassen werden können und alles einen einstelligen Namen hat.)