Prägnanz ist Macht

Mai 2002

| "Die Menge an Bedeutung, die in einem kleinen Raum durch algebraische Zeichen komprimiert wird, ist ein weiterer Umstand, der die Schlussfolgerungen erleichtert, die wir mit ihrer Hilfe zu ziehen gewohnt sind."

- Charles Babbage, zitiert in Iversons Turing Award Lecture

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

Pythons Ziel ist Regelmäßigkeit und Lesbarkeit, nicht Prägnanz.

Oberflächlich betrachtet scheint dies eine ziemlich vernichtende Aussage über eine Programmiersprache zu sein. Soweit ich das beurteilen kann, ist Prägnanz = Macht. Wenn ja, dann ergibt die Substitution:

Pythons Ziel ist Regelmäßigkeit und Lesbarkeit, nicht Macht.

und das scheint keine Abwägung zu sein (wenn es eine Abwägung ist), die man eingehen möchte. 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, die es wert wäre, direkt angegangen zu werden. Ich bin mir noch nicht sicher, ob die Antwort ein klares 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 nahe genug dran, dass man sie, außer in pathologischen Beispielen, als identisch behandeln kann.

Mir scheint, dass Prägnanz das ist, wofür Programmiersprachen da sind. Computer wären genauso glücklich, direkt in Maschinensprache angewiesen zu werden. Ich denke, der Hauptgrund, warum wir uns die Mühe machen, Hochsprachen zu entwickeln, ist, Hebelwirkung zu erzielen, damit wir in 10 Zeilen einer Hochsprache sagen (und wichtiger noch, denken) können, was 1000 Zeilen Maschinensprache erfordern würde. Mit anderen Worten, der Hauptzweck von Hochsprachen ist es, den Quellcode kleiner zu machen.

Wenn kleiner 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 unsere Programme macht.

Umgekehrt macht eine Sprache, die unsere Programme nicht klein macht, einen schlechten Job dessen, was Programmiersprachen tun sollen, wie ein Messer, das nicht gut schneidet, oder ein Druck, der unleserlich ist.

Metriken

Klein in welchem Sinne? Die gebräuchlichste Messung der Code-Größe sind Codezeilen. Aber ich denke, diese Metrik ist die gebräuchlichste, weil sie am einfachsten zu messen ist. Ich glaube nicht, dass irgendjemand wirklich glaubt, dass dies der wahre Test für die Länge eines Programms ist. Verschiedene Sprachen haben unterschiedliche Konventionen, wie viel man auf eine Zeile schreiben sollte; in C enthalten viele Zeilen nichts als ein oder zwei Trennzeichen.

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

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

Diese Metrik muss ausgearbeitet werden, und sie könnte im Fall spezifischer Sprachen Interpretation erfordern, aber ich denke, sie versucht, das Richtige zu messen, nämlich die Anzahl der Teile, die ein Programm hat. Ich denke, der Baum, den man bei dieser Übung zeichnen würde, ist das, was man sich im Kopf vorstellen muss, um das Programm zu konzipieren, und daher ist seine Größe proportional zu der Menge an Arbeit, die man leisten 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ägnanztests ist als Leitfaden für das Design von Sprachen. Der nützlichste Vergleich zwischen Sprachen ist zwischen zwei potenziellen Varianten derselben Sprache. Was kann ich in der Sprache tun, um Programme kürzer zu machen?

Wenn die konzeptionelle Last eines Programms proportional zu seiner Komplexität ist und ein bestimmter Programmierer eine feste konzeptionelle Last ertragen kann, dann ist dies dasselbe, als würde man fragen: Was kann ich tun, um Programmierern zu ermöglichen, am meisten zu erreichen? Und das scheint mir identisch mit der Frage: Wie entwerfe ich eine gute Sprache?

(Nebenbei bemerkt, nichts macht offensichtlicher, dass die alte Floskel "alle Sprachen sind äquivalent" falsch ist, als Sprachen zu entwerfen. Wenn man eine neue Sprache entwirft, vergleicht man ständig zwei Sprachen – die Sprache, wenn ich x tue, und wenn nicht – 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, neue Ideen zu finden. Wenn man etwas tun kann, das viele verschiedene Programme kürzer macht, ist das wahrscheinlich kein Zufall: Man hat wahrscheinlich eine nützliche neue Abstraktion entdeckt. Man könnte sogar ein Programm schreiben, das hilft, indem es den Quellcode nach wiederholten Mustern durchsucht. Unter den anderen Sprachen wären diejenigen mit dem Ruf nach Prägnanz diejenigen, nach denen man nach neuen Ideen suchen sollte: Forth, Joy, Icon.

Vergleich

Die erste Person, die über diese Themen schrieb, soweit ich weiß, war Fred Brooks im Mythical Man Month. Er schrieb, dass Programmierer unabhängig von der Sprache ungefähr die gleiche Menge an Code pro Tag generierten. Als ich das in meinen frühen Zwanzigern zum ersten Mal las, war es eine große Überraschung für mich und schien riesige Auswirkungen zu haben. Es bedeutete, dass (a) der einzige Weg, Software schneller zu schreiben, die Verwendung einer prägnanteren Sprache war und (b) jemand, der sich die Mühe machte, dies zu tun, Konkurrenten, die es nicht taten, hinter sich lassen konnte.

Brooks' Hypothese, wenn sie wahr ist, scheint im Herzen des Hackings zu liegen. In den Jahren danach habe ich jeder Evidenz, die ich bekommen konnte, aufmerksam Beachtung geschenkt, von formellen Studien bis hin zu Anekdoten über einzelne Projekte. Ich habe nichts gesehen, was ihm widerspricht.

Ich habe noch keine Evidenz gesehen, die mir schlüssig erschien, und erwarte sie auch nicht. Studien wie Lutz Prechelts Vergleich von Programmiersprachen, obwohl sie die Art von Ergebnissen generieren, die ich erwartet habe, neigen 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, deren Schreiben einen Monat dauert. Und der einzig wirkliche Test, wenn man, wie ich, glaubt, dass der Hauptzweck einer Sprache darin besteht, gut zum Denken zu sein (und nicht nur, um einem Computer zu sagen, was er tun soll, sobald man es sich überlegt hat), ist, welche neuen Dinge man darin schreiben kann. Jede Sprachvergleichsstudie, bei der man eine vordefinierte Spezifikation erfüllen muss, testet also etwas Falsches.

Der wahre Test einer Sprache ist, wie gut man neue Probleme entdecken und lösen kann, nicht wie gut man sie verwenden kann, um ein Problem zu lösen, das jemand anderes bereits formuliert hat. Diese beiden Kriterien sind sehr unterschiedlich. In der Kunst funktionieren Medien wie Stickerei und Mosaik gut, wenn man vorher weiß, was man machen will, aber absolut schlecht, wenn man es nicht weiß. Wenn man das Bild entdecken möchte, während man es erstellt – wie man es bei allem Komplexen wie einem Personenbild tun muss –, muss man ein flüssigeres Medium wie Bleistift, Tusche oder Ölfarbe verwenden. Und tatsächlich werden Wandteppiche und Mosaike in der Praxis so hergestellt, dass zuerst ein Gemälde erstellt und dann kopiert wird. (Das Wort "Cartoon" wurde ursprünglich verwendet, um ein für diesen Zweck bestimmtes Gemälde zu beschreiben).

Das bedeutet, dass wir wahrscheinlich nie genaue Vergleiche der relativen Macht von Programmiersprachen haben werden. Wir werden präzise Vergleiche haben, aber keine genauen. Insbesondere explizite Studien zum Zweck des Sprachvergleichs werden, da sie wahrscheinlich kleine Probleme verwenden und notwendigerweise vordefinierte Probleme verwenden, die Macht der mächtigeren Sprachen unterschätzen.

Berichte aus der Praxis werden, obwohl sie notwendigerweise weniger präzise sind als "wissenschaftliche" Studien, wahrscheinlich aussagekräftiger sein. Zum Beispiel hat Ulf Wiger von Ericsson eine Studie durchgeführt, die zu dem Schluss kam, dass Erlang 4-10x prägnanter als C++ und entsprechend schneller in der Softwareentwicklung ist:

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

Die Studie befasst sich auch explizit mit einem Punkt, der in Brooks' Buch nur implizit war (da er die Anzahl der debuggten Codezeilen maß): Programme, die in mächtigeren Sprachen geschrieben sind, haben tendenziell weniger Fehler. Das wird zu einem Selbstzweck, möglicherweise wichtiger als die Programmiererproduktivität, in Anwendungen wie Netzwerk-Switches.

Der Geschmackstest

Letztendlich muss man seinem Bauchgefühl folgen. Wie fühlt es sich an, in der Sprache zu programmieren? Ich denke, der Weg, die beste Sprache zu finden (oder zu entwerfen), besteht darin, hypersensibel dafür zu werden, wie gut eine Sprache einem das Denken ermöglicht, und dann die Sprache auszuwählen/zu entwerfen, die sich am besten anfühlt. Wenn ein Sprachmerkmal umständlich oder einschränkend ist, machen Sie sich keine Sorgen, Sie werden es merken.

Eine solche Hypersensibilität hat ihren Preis. Sie werden feststellen, dass Sie es nicht ertragen können, in unbeholfenen Sprachen zu programmieren. 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, zurück zu einer Sprache wechseln zu müssen, in der man den Typ jeder Variablen deklarieren muss und keine Liste von Objekten unterschiedlicher Typen erstellen kann.

Ich bin nicht der Einzige. Ich kenne viele Lisp-Hacker, denen das passiert ist. Tatsächlich könnte das genaueste Maß für die relative Macht von Programmiersprachen der Prozentsatz der Leute sein, die die Sprache kennen und jeden Job annehmen, bei dem sie diese Sprache verwenden dürfen, unabhängig vom Anwendungsbereich.

Einschränkung

Ich denke, die meisten Hacker wissen, was es bedeutet, wenn sich eine Sprache einschränkend anfühlt. Was passiert, wenn Sie dieses Gefühl haben? Ich denke, es ist dasselbe Gefühl, das man bekommt, wenn die Straße, die man nehmen möchte, blockiert ist und man einen langen Umweg nehmen muss, um dorthin zu gelangen, wo man hinwollte. 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 eine ist, die nicht prägnant genug ist. Das Problem ist nicht einfach, dass man nicht sagen kann, was man geplant hat. Es ist, dass der Umweg, den die Sprache einen machen lässt, länger ist. Versuchen Sie dieses Gedankenexperiment. Angenommen, 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 stattdessen erzwingen, es auf eine andere, kürzere Weise zu schreiben. Für mich zumindest würde sich das nicht sehr einschränkend anfühlen. Es wäre, als wäre die Straße, die man nehmen wollte, blockiert, und der Polizist an der Kreuzung würde einen stattdessen auf eine Abkürzung statt auf einen Umweg leiten. Großartig!

Ich denke, die meisten (neunzig Prozent?) des Gefühls der Einschränkung entstehen dadurch, dass man gezwungen wird, das Programm, das man in der Sprache schreibt, länger zu machen als eines, das man im Kopf hat. Einschränkung ist hauptsächlich mangelnde 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 begann, erwähnt zwei weitere Qualitäten: Regelmäßigkeit und Lesbarkeit. Ich bin mir nicht sicher, was Regelmäßigkeit ist oder welchen Vorteil, falls überhaupt, Code, der regelmäßig und lesbar ist, gegenüber Code hat, der lediglich lesbar ist. Aber ich denke, ich weiß, was mit Lesbarkeit gemeint ist, und ich denke, sie hängt auch mit Prägnanz zusammen.

Wir müssen hier vorsichtig sein, um zwischen der Lesbarkeit einer einzelnen Codezeile und der Lesbarkeit des gesamten Programms zu unterscheiden. Es ist das Letztere, das zählt. Ich stimme zu, dass eine Zeile Basic wahrscheinlich lesbarer ist als eine Zeile Lisp. Aber ein Programm, das in Basic geschrieben ist, wird mehr Zeilen haben als dasselbe Programm, das in Lisp geschrieben ist (besonders sobald man nach Greenspunland übergeht). Die Gesamtanstrengung beim Lesen des Basic-Programms wird sicherlich größer sein.

Gesamtanstrengung = Anstrengung pro Zeile x Anzahl der Zeilen

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

Was Lesbarkeit pro Zeile für den Benutzer bedeutet, der die Sprache zum ersten Mal kennenlernt, ist, dass der Quellcode unbedrohlich aussieht. Lesbarkeit pro Zeile könnte also eine gute Marketingentscheidung sein, auch wenn es eine schlechte Designentscheidung ist. Es ist isomorph zur sehr erfolgreichen Technik, Leute in Raten bezahlen zu lassen: Anstatt sie mit einem hohen anfänglichen Preis zu erschrecken, sagt man ihnen die niedrige monatliche Zahlung. Ratenzahlungen sind jedoch ein Nettoverlust für den Käufer, genauso wie bloße Lesbarkeit pro Zeile wahrscheinlich für den Programmierer ist. Der Käufer wird viele dieser niedrigen, niedrigen Zahlungen leisten; und der Programmierer wird viele dieser einzeln lesbaren Zeilen lesen.

Diese Abwägung gibt es schon vor Programmiersprachen. Wenn Sie daran gewöhnt sind, Romane und Zeitungsartikel zu lesen, kann Ihre erste Erfahrung mit dem Lesen eines Matheaufsatzes entmutigend sein. Es kann eine halbe Stunde dauern, eine einzige Seite zu lesen. Und doch bin ich ziemlich sicher, dass die Notation nicht das Problem ist, auch wenn es sich so anfühlen mag. Der Matheaufsatz ist schwer zu lesen, weil die Ideen schwer sind. Wenn man dieselben Ideen in Prosa ausdrücken würde (wie es Mathematiker tun mussten, bevor sie prägnante Notationen entwickelten), wären sie nicht leichter zu lesen, weil der Aufsatz auf die Größe eines Buches anwachsen würde.

In welchem Ausmaß?

Eine Reihe von Leuten hat die Idee verworfen, dass Prägnanz = Macht ist. Ich denke, es wäre nützlicher, anstatt einfach zu argumentieren, dass sie gleich sind oder nicht, zu fragen: In welchem Ausmaß ist Prägnanz = Macht? Denn offensichtlich ist Prägnanz ein großer Teil dessen, wofür Hochsprachen da sind. Wenn sie nicht alles sind, wofür sie da sind, wofür sind sie dann noch da und wie wichtig sind diese anderen Funktionen relativ gesehen?

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

Die Hypothese, mit der ich begann, war, dass ich dachte, dass Prägnanz, außer in pathologischen Beispielen, als identisch mit Macht betrachtet werden könnte. Was ich meinte war, dass sie in jeder Sprache, die jemand entwerfen würde, identisch wären, aber dass, wenn jemand eine Sprache explizit entwerfen wollte, um diese Hypothese zu widerlegen, er es wahrscheinlich tun könnte. Ich bin mir nicht einmal darüber sicher, tatsächlich.

Sprachen, nicht Programme

Wir sollten klarstellen, dass wir über die Prägnanz von Sprachen sprechen, nicht von einzelnen Programmen. 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 viele Male seine eigene Länge einsparen, um gerechtfertigt zu sein. Wenn das Schreiben eines komplizierten Makros Ihnen jedes Mal, wenn Sie es verwenden, zehn Zeilen Code spart und das Makro selbst zehn Zeilen Code ist, dann erzielen Sie eine Nettoeinsparung an Zeilen, wenn Sie es mehr als einmal verwenden. Aber das könnte immer noch ein schlechter Schachzug sein, weil Makrodefinitionen schwerer zu lesen sind als normaler Code. Sie müssen das Makro vielleicht zehn- oder zwanzigmal verwenden, bevor es eine Nettoverbesserung der Lesbarkeit bringt.

Ich bin sicher, jede Sprache hat solche Abwägungen (obwohl ich vermute, dass die Einsätze höher werden, je mächtiger die Sprache wird). Jeder Programmierer muss Code gesehen haben, den eine clevere Person durch zweifelhafte Programmiertricks marginal kürzer gemacht hat.

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

Ein Grund, warum es schwer vorstellbar ist, dass eine Sprache zu prägnant ist, ist, dass es, wenn es eine übermäßig kompakte Art gäbe, etwas auszudrücken, wahrscheinlich auch eine längere gäbe. Wenn Sie zum Beispiel der Meinung wären, dass Lisp-Programme, die viele Makros oder höherwertige Funktionen verwenden, zu dicht sind, könnten Sie, wenn Sie möchten, Code schreiben, der isomorph zu Pascal ist. Wenn Sie Fakultät in Arc nicht als Aufruf einer höherwertigen Funktion (rec zero 1 * 1-) ausdrücken möchten, können Sie auch eine rekursive Definition ausschreiben: (rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))) Obwohl mir aus dem Stegreif keine Beispiele einfallen, bin ich an der Frage interessiert, ob eine Sprache zu prägnant sein könnte. Gibt es Sprachen, die Sie zwingen, Code auf eine Weise zu schreiben, die verkrampft und unverständlich ist? Wenn jemand Beispiele hat, würde ich sie sehr gerne sehen.

(Erinnerung: Was ich suche, sind Programme, die nach der oben skizzierten Metrik der "Elemente" sehr dicht sind, nicht nur Programme, die kurz sind, weil Trennzeichen weggelassen werden können und alles einen Ein-Zeichen-Namen hat.)