PRÄGNANZ IST MACHT
OriginalMai 2002
"Die Menge an Bedeutung, die durch algebraische Zeichen in einem kleinen Raum komprimiert wird, ist ein weiterer Umstand, der die Überlegungen erleichtert, die wir gewohnt sind, mit ihrer Hilfe durchzuführen."
- Charles Babbage, zitiert in Iversons Turing Award Lecture
In der Diskussion über die Themen, die durch Revenge of the Nerds auf der LL1-Mailingliste aufgeworfen wurden, schrieb Paul Prescod etwas, das mir im Gedächtnis geblieben ist.
Pythons Ziel 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 sehen kann, ist Prägnanz = Macht. Wenn dem so ist, dann ersetzen wir und erhalten:
Pythons Ziel ist Regelmäßigkeit und Lesbarkeit, nicht Macht.
Und dies scheint kein Kompromiss (wenn es denn einer ist), den man eingehen möchte. Es ist nicht weit entfernt davon zu sagen, dass Pythons Ziel nicht ist, als Programmiersprache effektiv zu sein.
Ist Prägnanz = Macht? Dies scheint mir eine wichtige Frage zu sein, vielleicht die wichtigste Frage für jeden, der an Sprachdesign interessiert ist, und eine, der es sich lohnt, direkt zu stellen. Ich bin mir noch nicht sicher, ob die Antwort ein einfaches Ja ist, aber es scheint eine gute Hypothese zu sein, mit der man beginnen kann.
Hypothese
Meine Hypothese ist, dass Prägnanz Macht ist oder ihr so nahe kommt, dass man sie, außer in pathologischen Beispielen, als identisch behandeln kann.
Es scheint mir, dass Prägnanz der Zweck von Programmiersprachen ist. Computer wären genauso glücklich, 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, um Hebel zu bekommen, so dass wir in 10 Zeilen einer Hochsprache sagen (und vor allem denken) können, was in 1000 Zeilen Maschinensprache erforderlich wäre. Mit anderen Worten, der Hauptzweck von Hochsprachen ist es, den Quellcode kleiner zu machen.
Wenn kleinerer Quellcode der Zweck von Hochsprachen ist und die Macht von etwas darin besteht, wie gut es seinen Zweck erfüllt, dann ist das Maß für die Macht einer Programmiersprache, wie klein sie Ihre Programme macht.
Umgekehrt ist eine Sprache, die Ihre Programme nicht klein macht, schlecht in dem, was Programmiersprachen eigentlich tun sollen, wie ein Messer, das nicht gut schneidet, oder eine Druckschrift, die unleserlich ist.
Metriken
Aber was ist unter "klein" zu verstehen? Das gängigste Maß für die Codegröße sind Codezeilen. Aber ich denke, dass diese Metrik die gängigste ist, weil sie am einfachsten zu messen ist. Ich glaube nicht, dass irgendjemand wirklich glaubt, dass es der wahre Test für die Länge eines Programms ist. Verschiedene Sprachen haben unterschiedliche Konventionen dafür, wie viel man in eine Zeile packen sollte; in C haben viele Zeilen nichts weiter als einen Trenner oder zwei.
Ein anderer einfacher Test ist die Anzahl der Zeichen in einem Programm, aber auch das ist nicht sehr gut; manche Sprachen (wie 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 einen eigenen Knoten ergäbe, wenn man einen Baum darstellen würde, der den Quellcode repräsentiert. Der Name einer Variablen oder Funktion ist ein Element; eine ganze Zahl oder eine Gleitkommazahl ist ein Element; ein Stück Literaltext 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 eines?), aber ich denke, die meisten sind für jede Sprache gleich, so dass sie Vergleiche nicht stark beeinflussen.
Diese Metrik muss noch weiter ausgearbeitet werden, und es könnte in Bezug auf bestimmte Sprachen Interpretationsbedarf geben, 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 man in dieser Übung zeichnen würde, ist das, was man im Kopf haben muss, um das Programm zu konzipieren, und seine Größe ist proportional zum Arbeitsaufwand, den man aufwenden muss, um es zu schreiben oder zu lesen.
Design
Diese Art von Metrik würde es uns ermöglichen, verschiedene Sprachen zu vergleichen, aber das ist, zumindest für mich, nicht ihr Hauptwert. Der Hauptwert des Prägnanz-Tests liegt darin, dass er als Leitfaden beim Entwerfen von Sprachen dient. 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 konzeptuelle Belastung eines Programms proportional zu seiner Komplexität ist und ein bestimmter Programmierer eine feste konzeptuelle Belastung verkraften kann, dann ist dies dasselbe wie die Frage: Was kann ich tun, um Programmierer in die Lage zu versetzen, das Meiste zu erreichen? Und das scheint mir identisch mit der Frage: Wie kann ich eine gute Sprache entwerfen?
(Nebenbei bemerkt, nichts macht deutlicher, dass das alte Sprichwort "alle Sprachen sind gleichwertig" falsch ist, als das Entwerfen von Sprachen. Wenn man eine neue Sprache entwirft, vergleicht man ständig zwei Sprachen - die Sprache, wenn ich x tue, und wenn ich es nicht tue -, um zu entscheiden, welche besser ist. Wenn dies wirklich eine bedeutungslose Frage wäre, könnte man genauso gut eine Münze werfen.)
Das Streben nach Prägnanz scheint ein guter Weg zu sein, um neue Ideen zu finden. Wenn man etwas tun kann, das viele verschiedene Programme kürzer macht, ist es wahrscheinlich kein Zufall: Man hat wahrscheinlich eine nützliche neue Abstraktion entdeckt. Man könnte sogar ein Programm schreiben, um dabei zu helfen, indem man den Quellcode nach sich wiederholenden Mustern durchsucht. Unter den anderen Sprachen wären diejenigen, die für ihre Prägnanz bekannt sind, die, von denen man neue Ideen erwarten könnte: Forth, Joy, Icon.
Vergleich
Der Erste, der über diese Themen geschrieben hat, soweit ich weiß, war Fred Brooks in "The Mythical Man Month". Er schrieb, dass Programmierer scheinbar etwa die gleiche Menge an Code pro Tag produzierten, unabhängig von der Sprache. Als ich das in meinen frühen Zwanzigern zum ersten Mal las, war es für mich eine große Überraschung und schien riesige Auswirkungen zu haben. Das bedeutete (a), dass der einzige Weg, Software schneller zu schreiben, darin bestand, eine prägnantere Sprache zu verwenden, und (b), dass jemand, der sich die Mühe machte, dies zu tun, Konkurrenten, die das nicht taten, abschütteln könnte.
Brooks' Hypothese, wenn sie denn stimmt, scheint im Herzen des Hackens zu stehen. In den Jahren seitdem habe ich genau auf jedes Indiz geachtet, das ich dazu bekommen konnte, von formalen Studien bis hin zu Anekdoten über einzelne Projekte. Ich habe nichts gefunden, was ihm widerspricht.
Ich habe noch keine Beweise gesehen, die mir meiner Meinung nach schlüssig erschienen sind, und ich erwarte auch keine. Studien wie der Vergleich von Programmiersprachen von Lutz Prechelt, die zwar die Art von Ergebnissen liefern, die ich erwartet habe, tendieren dazu, Probleme zu verwenden, die zu kurz sind, um aussagekräftige Tests zu sein. Ein besserer Test für eine Sprache ist, was in Programmen passiert, die einen Monat zu schreiben dauern. Und der einzige wirkliche Test, wenn man glaubt, wie ich, dass der Hauptzweck einer Sprache darin besteht, gut zum Denken zu sein (anstatt nur dem Computer zu sagen, was er tun soll, sobald man es sich ausgedacht hat), ist, was Neues man damit schreiben kann.
Jeder Sprachvergleich, bei dem man eine vorgegebene Spezifikation erfüllen muss, testet also etwas leicht Falsches.
Der wahre Test einer Sprache ist, wie gut man neue Probleme entdecken und lösen kann, nicht wie gut man sie nutzen kann, um ein Problem zu lösen, das jemand anderes bereits formuliert hat. Diese beiden Kriterien sind ganz unterschiedlich.
In der Kunst funktionieren Medien wie Stickerei und Mosaik gut, wenn man vorher weiß, was man herstellen möchte, sind aber absolut schlecht, wenn man das nicht weiß. Wenn man das Bild entdecken möchte, während man es herstellt - wie man es zum Beispiel bei etwas so Komplexem wie einem Bild einer Person tun muss -, muss man ein flüssigeres Medium wie Bleistift oder Tuschelavage oder Ölfarbe verwenden. Und tatsächlich wird in der Praxis Tapisserien und Mosaike 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 gedacht war).
Das bedeutet, dass wir wahrscheinlich nie genaue Vergleiche der relativen Leistungsfähigkeit von Programmiersprachen haben werden. Wir werden präzise Vergleiche haben, aber keine genauen. Insbesondere werden explizite Studien zum Zweck des Sprachvergleichs, da sie wahrscheinlich kleine Probleme verwenden werden und notwendigerweise vorgegebene Probleme verwenden werden, dazu neigen, die Leistungsfähigkeit der leistungsfähigeren Sprachen zu unterschätzen.
Berichte aus der Praxis, obwohl sie notwendigerweise weniger präzise sein werden als "wissenschaftliche" Studien, sind wahrscheinlich aussagekräftiger. Zum Beispiel kam Ulf Wiger von Ericsson in einer Studie zu dem Schluss, dass Erlang 4-10 Mal prägnanter als C++ ist und proportional schneller Software entwickelt werden kann:
Vergleiche zwischen Ericsson-internen Entwicklungsprojekten deuten auf eine ähnliche Zeilen/Stunde-Produktivität hin, 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 Volumen des Quellcodes.
Die Studie befasst sich auch explizit mit einem Punkt, der in Brooks' Buch nur implizit war (da er debuggten Code in Zeilen maß): Programme, die in leistungsfähigeren Sprachen geschrieben sind, neigen dazu, weniger Fehler zu haben. Das kann selbst zum Ziel werden, möglicherweise wichtiger als die Produktivität der Programmierer, in Anwendungen wie Netzwerkswitches.
Der Geschmackstest
Letztendlich denke ich, man muss auf sein Bauchgefühl hören. Wie fühlt es sich an, in der Sprache zu programmieren? Ich denke, der Weg, um (oder zu entwerfen) die beste Sprache zu finden, ist, überempfindlich dafür zu werden, wie gut eine Sprache es einem ermöglicht, zu denken, und dann die Sprache zu wählen/zu entwerfen, die sich am besten anfühlt. Wenn irgendein Sprachmerkmal unbeholfen oder einschränkend ist, machen Sie sich darüber keine Sorgen, Sie werden es merken.
Eine solche Überempfindlichkeit wird einen Preis kosten. Sie werden feststellen, dass Sie das Programmieren in unbeholfenen Sprachen einfach nicht ertragen können. Ich finde es unerträglich einschränkend, in Sprachen ohne Makros zu programmieren, genauso wie jemand, der an dynamische Typisierung gewöhnt ist, es unerträglich einschränkend findet, wieder in einer Sprache programmieren zu müssen, in der man den Typ jeder Variablen deklarieren und keine Liste von Objekten unterschiedlicher Typen erstellen kann.
Ich bin nicht der Einzige. Ich kenne viele Lisp-Hacker, denen es so ergangen ist. Tatsächlich könnte das genaueste Maß für die relative Leistungsfähigkeit von Programmiersprachen der Prozentsatz der Menschen sein, die die Sprache kennen und jeden Job annehmen würden, bei dem sie diese Sprache verwenden dürfen, unabhängig vom Anwendungsbereich.
Einschränkung
Ich denke, die meisten Hacker wissen, was es bedeutet, wenn eine Sprache sich einschränkend anfühlt. Was passiert, wenn man dieses Gefühl hat? Ich denke, es ist dasselbe Gefühl, das man bekommt, wenn die Straße, die man nehmen möchte, gesperrt ist und man einen langen Umweg nehmen muss, um an den gewünschten Ort zu kommen. Es gibt etwas, das man sagen möchte, und die Sprache lässt es nicht zu.
Was hier wirklich vor sich geht, denke ich, ist, dass eine einschränkende Sprache nicht prägnant genug ist. Das Problem ist nicht einfach, dass man nicht sagen kann, was man geplant hatte. Es ist, dass der Umweg, den die Sprache einen gehen lässt, länger ist. Versuchen Sie dieses Gedankenexperiment. Nehmen Sie an, es gäbe ein Programm, das Sie schreiben möchten, und die Sprache würde es Ihnen nicht erlauben, es so auszudrücken, wie Sie es geplant hatten, sondern Sie stattdessen zwingen, das Programm auf eine andere Art und Weise zu schreiben, die kürzer wäre. Für mich zumindest würde sich das nicht sehr einschränkend anfühlen. Es wäre, als würde der Polizist an der Kreuzung Sie statt zu einem Umweg zu einem Abkürzungsweg schicken. Toll!
Ich denke, die meisten (neunzig Prozent?) des Gefühls der Einschränkung kommen daher, dass man gezwungen wird, das Programm, das man in der Sprache schreibt, länger zu machen, als es in seinem Kopf ist. Einschränkung ist hauptsächlich Mangel an Prägnanz. Wenn sich eine Sprache also einschränkend anfühlt, bedeutet das (hauptsächlich), dass sie nicht prägnant genug ist, und wenn eine Sprache nicht prägnant ist, wird sie sich einschränkend anfühlen.
Lesbarkeit
Das Zitat, mit dem ich begonnen habe, erwähnt zwei weitere Qualitäten: Regelmäßigkeit und Lesbarkeit. Ich bin mir nicht sicher, was Regelmäßigkeit ist oder welchen Vorteil, wenn überhaupt, Code, der regelmäßig und lesbar ist, gegenüber Code hat, der einfach nur lesbar ist. Aber ich denke, ich weiß, was mit Lesbarkeit gemeint ist, und ich denke, das hängt auch mit Prägnanz zusammen.
Wir müssen hier vorsichtig sein, um die Lesbarkeit einer einzelnen Codezeile von der Lesbarkeit des gesamten Programms zu unterscheiden. Es ist die zweite, die zählt. Ich stimme zu, dass eine Zeile Basic wahrscheinlich lesbarer sein wird als eine Zeile Lisp. Aber ein in Basic geschriebenes Programm wird mehr Zeilen haben als das gleiche Programm in Lisp (vor allem wenn man in Greenspunland kommt). Der Gesamtaufwand zum Lesen des Basic-Programms wird sicher größer sein.
Gesamtaufwand = Aufwand pro Zeile x Anzahl der Zeilen
Ich bin mir nicht so sicher, dass Lesbarkeit direkt proportional zur Prägnanz ist, wie ich es bei der Leistungsfähigkeit bin, aber Prägnanz ist sicher ein Faktor (im mathematischen Sinne; siehe obige Gleichung) bei der Lesbarkeit. Es könnte also sogar sinnlos sein, zu sagen, dass das Ziel einer Sprache Lesbarkeit und nicht Prägnanz ist; es wäre, als würde man sagen, das Ziel sei Lesbarkeit, nicht Lesbarkeit.
Was Lesbarkeit pro Zeile für den Benutzer bedeutet, der die Sprache zum ersten Mal kennenlernt, ist, dass der Quellcode unbedrohlich aussehen wird. Daher könnte Lesbarkeit pro Zeile eine gute Marketingentscheidung sein, auch wenn es eine schlechte Designentscheidung ist. Es ist isomorph zu der sehr erfolgreichen Technik, den Leuten Ratenzahlungen zu ermöglichen: Anstatt sie mit einem hohen Vorauszahlungspreis zu erschrecken, sagen Sie ihnen die niedrige monatliche Zahlung. Ratenzahlungspläne sind jedoch ein Nettoverlust für den Käufer, genauso wie reine Lesbarkeit pro Zeile wahrscheinlich für den Programmierer ist. Der Käufer wird sehr viele dieser niedrigen, niedrigen Zahlungen leisten müssen; und der Programmierer wird sehr viele dieser einzeln lesbaren Zeilen lesen müssen.
Dieser Zielkonflikt ist älter als Programmiersprachen. Wenn man an das Lesen von Romanen und Zeitungsartikeln gewöhnt ist, kann die erste Erfahrung des Lesens eines mathematischen Aufsatzes entmutigend sein. Es könnte eine halbe Stunde dauern, eine einzige Seite zu lesen. Und doch bin ich ziemlich sicher, dass nicht die Notation das Problem ist, auch wenn es sich so anfühlen mag. Der mathematische Aufsatz ist schwer zu lesen, weil die Ideen schwierig sind. Wenn man dieselben Ideen in Prosa ausdrücken würde (wie Mathematiker es tun mussten, bevor sie prägnante Notationen entwickelten), wären sie nicht leichter zu lesen, denn der Aufsatz würde zu einem Buch anwachsen.
In welchem Ausmaß?
Eine Reihe von Leuten haben die Idee abgelehnt, dass Knappheit = Macht ist. Ich denke, es wäre nützlicher, anstatt einfach zu argumentieren, dass sie dasselbe sind oder nicht, zu fragen: In welchem Ausmaß ist Knappheit = Macht? Denn offensichtlich ist Knappheit ein großer Teil dessen, wofür Hochsprachen da sind. Wenn es nicht alles ist, wofür sie da sind, was sind dann die anderen Funktionen und wie wichtig sind diese anderen Funktionen im Verhältnis dazu?
Ich schlage dies nicht nur vor, um die Debatte zivilisierter zu gestalten. Ich möchte wirklich die Antwort wissen. Wann, wenn überhaupt, ist eine Sprache zu knapp für ihren eigenen Zweck?
Die Hypothese, mit der ich begonnen habe, war, dass Knappheit, außer in pathologischen Beispielen, als identisch mit Macht betrachtet werden könnte. Was ich damit meinte, war, dass in jeder Sprache, die jemand entwerfen würde, sie identisch wären, aber dass, wenn jemand eine Sprache entwerfen wollte, um diese Hypothese zu widerlegen, er das wahrscheinlich tun könnte. Ich bin mir da sogar selbst nicht sicher.
Sprachen, nicht Programme
Wir sollten klar sein, dass wir über die Knappheit von Sprachen, nicht von einzelnen Programmen, sprechen. Es ist sicherlich möglich, dass einzelne Programme zu dicht geschrieben werden.
Ich habe darüber in On Lisp geschrieben. Ein komplexes Makro muss möglicherweise seine eigene Länge um ein Vielfaches übertreffen, um gerechtfertigt zu sein. Wenn das Schreiben eines kniffligen Makros Ihnen zehn Codezeilen pro Verwendung ersparen könnte und das Makro selbst zehn Codezeilen lang ist, dann erhalten Sie einen Nettogewinn an Codezeilen, wenn Sie es mehr als einmal verwenden. Das könnte jedoch immer noch ein schlechter Schritt sein, da Makrodefinitionen schwerer zu lesen sind als normaler Code. Möglicherweise müssen Sie das Makro zehn- oder zwanzigmal verwenden, bevor es zu einer Nettoverbesserung der Lesbarkeit führt.
Ich bin sicher, jede Sprache hat solche Zielkonflikte (auch wenn die Einsätze meiner Meinung nach höher werden, je mächtiger die Sprache ist). Jeder Programmierer muss Code gesehen haben, den eine clevere Person durch den Einsatz fragwürdiger Programmiertricks geringfügig kürzer gemacht hat.
Also darüber gibt es keinen Streit - zumindest nicht von mir. Einzelne Programme können sicherlich zu knapp für ihren eigenen Zweck sein. Die Frage ist, kann eine Sprache das auch? Kann eine Sprache Programmierer dazu zwingen, Code zu schreiben, der kurz (in Elementen) ist, auf Kosten der allgemeinen Lesbarkeit?
Ein Grund, warum es schwer vorstellbar ist, dass eine Sprache zu knapp sein könnte, ist, dass es, wenn es eine übermäßig kompakte Art gäbe, etwas auszudrücken, wahrscheinlich auch einen längeren Weg gäbe. Wenn Sie zum Beispiel das Gefühl hätten, dass Lisp-Programme, die viele Makros oder höhere Ordnungsfunktionen verwenden, zu dicht sind, könnten Sie, wenn Sie es vorziehen, Code schreiben, der isomorph zu Pascal ist. Wenn Sie die Fakultät in Arc nicht als Aufruf einer höheren Ordnungsfunktion ausdrücken möchten:
(rec zero 1 * 1-)
können Sie auch eine rekursive Definition schreiben:
(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 knapp sein könnte. Gibt es Sprachen, die einen dazu zwingen, Code auf eine verkrampfte und unverständliche Art und Weise zu schreiben? Wenn jemand Beispiele hat, wäre ich sehr interessiert, sie zu sehen.
(Erinnerung: Was ich suche, sind Programme, die nach dem oben skizzierten Maßstab der "Elemente" sehr dicht sind, nicht einfach nur kurze Programme, bei denen Trennzeichen weggelassen und alles einsilbig benannt werden kann.)