Es gibt in der Bewegung für freie Software einen treffenden Satz: Wir stehen auf den Schultern von Riesen, d. h. wir profitieren von der Vorarbeit früherer Generationen von Programmierern.
Von Rolf Mackowiak
Es gibt in der Bewegung für freie Software einen treffenden Satz: Wir stehen auf den Schultern von Riesen, d. h. wir profitieren von der Vorarbeit früherer Generationen von Programmierern.
„Mein Computer versteht mich nicht!“ könnte ein Stoßseufzer sein, wenn der Rechner mal wieder Zicken macht. Ganz klar, daß der Fehler beim Bediener liegt. Um dem Rechner in die Spur zu bringen, gibt es Programmiersprachen.
Gehen wir zuerst einmal in die Grundlagen. Die CPU, also die Recheneinheit, versteht nur 0 und 1. Ein sehr beschränkter Wortschatz. Da es niemanden gibt, der damit komplexe Systeme programmieren kann, gab es schon früh den Versuch, diese Übersetzung eben einem Programm zu überlassen.
Assembler ist eine sehr maschinennahe Art, zu programmieren. Es gibt für die Funktionen, die die CPU ausführen soll, mnemonische Codes. Das ist immer noch sehr schwer zu lesen, wenn ein Mensch die Anweisungen verstehen soll.
Die nächste Ebene der Programmiersprachen versuchte daher, von der Funktionalität der CPU zu abstrahieren. Algol und Fortran waren unter den ersten Vertretern dieser Sprachen. Fortran hat sich bis heute im wissenschaftlichen Bereich gehalten, weil es viele Programme gibt, die damit entwickelt wurden. Das Rad muß ja nicht neu erfunden werden.
Es wurde lange zwischen Interpreter- und Compilersprachen unterschieden. Der klassische Vertreter der Interpreter-Sprachen war BASIC, für die Compiler-Seite C, später C++. Was macht den Unterschied?
Interpreter-Sprachen (heute meist Skript-Sprachen genannt), brauchen zur Ausführung eben diesen Interpreter. Erst durch ihn kann das Programm gestartet und ausgeführt werden. Der sogenannte Quelltext wird zeilenweise gelesen und ausgeführt. Dadurch wird die Ausführung des Programms sehr verlangsamt.
Auch ein Compiler liest den Quelltext zeilenweise, aber im Gegensatz zum Interpreter erzeugt er ein Gesamtpaket aus eigenständig ablaufendem Code, der sich dann einfach als separates Programm starten läßt, ohne den Compiler zu benötigen. Ihre Textverarbeitung ist beispielsweise ein solches Programm.
Einen weiteren Unterschied gibt es bei der Entwicklung von Programmen. Wird ein fehlerhaftes Programm im Interpreter gestartet, bekommt man eine Fehlermeldung, die auf die Art des Fehlers hinweist. Auch ein Compiler reagiert so. Früher war es so, daß man dann den Quelltext in einen Editor laden, den Fehler im Quelltext beseitigen und dann erneut kompilieren mußte. Bis am Ende dann ein lauffähiges Programm dabei herauskam. Das beschränkte sich am Anfang auf Programmfelder, die weitgehend textorientiert waren. Anfangs waren Fernschreiber das Ausgabemedium, weswegen die Schnittstelle noch heute das Kürzel TTY trägt.
Was ist ein Editor? Im Englischen unterscheidet man zwischen Editor und Wordprocessor. Letzeres ist die allen bekannte Textverarbeitung, z. B. Word. Editoren schreiben nur reinen Text, ohne Schriftattribute wie fett, kursiv usw. Nur das können Compiler verarbeiten.
Heute gibt es sogenannte IDEs, die Editor, Compiler und Debugger zusammenfassen und das Ganze etwas handlicher machen.
Die Ausführungsgeschwindigkeit spricht ganz eindeutig für Compiler-Sprachen. Warum gibt es dann noch Interpreter-Sprachen? Ein Punkt ist die Portabilität von Programmen, d. h. die Ausführbarkeit auf unterschiedlichen Rechner-Systemen. Compiler-Programme sind gewissermaßen auf das System fixiert, für den das Programm geschrieben wurde. Ob die Unterschiede nun auf der CPU-Architektur oder dem verwendeten Betriebssystem beruhen, ist dabei ziemlich egal.
Um sich davon unabhängig zu machen, wurde Java entwickelt. Ein Java-Programm läuft auf allen Systemen, die ein entsprechendes Java Runtime Environment haben. Java ist gewissermaßen ein Zwitter. Der Quelltext wird nicht direkt in Maschinencode umgewandelt, sondern in eine Zwischenstufe, den sogenannten Byte-Code. Dies macht der sogenannte Jitter, der Programmcode erst dann übersetzt, wenn er vom Programm aufgerufen wird, eine Just-in-time Kompilierung – daher der Name.
Diese Universalität hat aber sein Kosten in Form einer langsameren Ausführung. Bei der heute verfügbaren Rechnerleistung ist das aber eher ein Problem für Puristen.
Skript-Sprachen haben demgegenüber einen großen Vorteil: Sie eignen sich für kleinere Aufgaben, die „so nebenbei“ erledigt werden. Sie sind aber durchaus nicht darauf beschränkt. Perl oder Python sind Beispiele dafür.
Warum gibt es so viele unterschiedliche Sprachen? Jede Programmiersprache habe ihren eigenen „Flavor“, heißt es. Damit ist gemeint, daß sich jede Sprache anders „anfühlt“. Mein persönlicher Favorit ist Python, die von Guido van Rossum entwickelt wurde, aber über eine weltweite Entwickler-Gemeinde verfügt.
Während Programm-Code in vielen Sprachen durch Klammern strukturiert wird, erfolgt sie in Python durch die Einrückung von Code-Zeilen. Zeilen mit derselben Einrückungstiefe gehören zu einem Block, z. B. einer Funktions-Definition. Das ergibt schon optisch eine klare Struktur, die zu einem entsprechenden Programmierstil einlädt. Python-Code ist entsprechend leicht lesbar.
Ich habe in der letzten Zeit in zwei andere Sprachen hineingeschnuppert, die beide auf LISP basieren, aber durchaus eigenständig sind: Lush und Racket. Mit Lush entwickelte Programme kann man – in Grenzen – in C-Quellcode umwandeln und dann kompilieren. Racket besticht durch seine fast chamäleonartige Anpassungsfähigkeit. Allein die Dokumentation umfaßt mehr als 200 MB – ich habe also noch jede Menge zu lesen und zu lernen.
Pascal ist eine Sprache, die vor allem als Turbo-Pascal verbreitet war. Das war vor der Zeit grafischer Benutzeroberflächen, sie wurde nur simuliert. Aber es war schon ein deutlich einfacheres Arbeiten möglich, da auch hier Editor und Compiler integriert waren. Dabei war Pascal nie für eine praktische Anwendung gedacht, sondern nur als Lehrsprache mit entsprechend klaren Strukturen. Entwickelt wurde sie von Niklaus Wirth an der ETH Zürich.
Eine andere Sprache, die es mir sehr angetan hatte, ist LOGO. Sie wurde von dem Erziehungswissenschaftler Seymour Papert speziell für Kinder entwickelt. Ein hervorstechendes Merkmal war die sogenannte Turtle-Grafik. Normalerweise werden kartesische Koordinaten verwendet, also in x- und y-Koordinaten angegeben. Bei der Turtle-Grafik ist das anders. Es gibt einen Grafik-Cursor, die Turtle. Den Namen hat sie daher, daß Papert kleine Maschinen entwarf, die die Befehle umsetzten und ein wenig an Schildkröten erinnerten. Ich habe einmal einen Film gesehen, in dem kleine Kinder auf dem Boden herumrutschten und die Bewegungen machten, die auch die Turtle ausführen sollte. Ihr Enthusiasmus dabei war beeindruckend.
Was macht Turtle-Grafik so anders? Sie beruht nicht vordringlich auf absoluten Koordinaten, sondern auf relativen, also vom Startpunkt aus gesehene Bewegungen. Um z. B. ein Quadrat zu zeichnen, reichte die Eingabe von „forward 100, right 90“ und das brauchte dann nur viermal eingegeben zu werden. Das konnte man dann in einer benannten Funktion zusammenfassen und mit deren Namen dann einfach ein Quadrat zeichnen. Leider hatten die damaligen Rechner für den Heim- und Hobbybereich zu wenig Speicher, um mit LOGO größere Programme zu schreiben. Aber sie hatte schon Routinen für die Fehlerbehandlung (Exceptions), die ihrer Zeit weit voraus waren. Heute sind sie Standard.
Daneben gibt es auch Sprachen, die nur eine beschränkte Zielsetzung hatten. Forth ist so eine Sprache, die ursprünglich zur Steuerung von Radioteleskopen diente. Hatte man ein Programm in Forth geschrieben, dann konnte man das System speichern und hatte dann beim nächsten Start des Systems eine erweiterte Programmiersprache zur Verfügung, ähnlich wie bei Smalltalk.
Man kann Programmiersprachen aber auch unter einem anderen Blickwinkel betrachten. Die ersten Programmiersprachen waren auf Funktionen und Prozeduren beschränkt. Mit der Entwicklung eines objekt-orientierten Ansatzes hat sich das erweitert. Insbesondere das Konzept des Objekts hat die Möglichkeiten deutlich erweitert.
Es gab z. B. im erwähnten Pascal die Möglichkeit, aus den vorhandenen Datentypen einen neuen zu definieren. So konnte man einen Datentyp „Person“ definieren, der z. B. über die Attribute Vorname, Nachname, Adresse verfügte. Wobei das Attribut Adresse ebenfalls ein neu definierter Verbund aus Wohnort, Straße etc. sein konnte.
Objekte bzw. Klassen sind eine Erweiterung dieses Konzepts. Objekte sind Datentypen mit Methoden, also Funktionen. Bei der funktionalen Programmierung galt die Trennung von Daten und Programm. Eine Funktion machte etwas mit den Daten. Eine Klasse hat Daten und Funktionen, diese zu bearbeiten und die werden dann Methoden genannt. Von den damit geschaffenen Datentypen lassen sich weitere herleiten. Das nennt sich dann Vererbung. Es können für dieses neue Objekt zusätzliche Eigenschaften und Methoden definiert werden.
Eine Klasse ist sozusagen die Blaupause für ein Objekt. Man kann sie nicht direkt verwenden, sondern muß eine Variable definieren, mit der man dann arbeiten kann und die über alle Eigenschaften und Methoden verfügt, die in der Klasse festgelegt sind.
Daneben gibt es auch Sprachen, die zwar eigenständig ausgeführt werden, aber so spezifisch sind, daß sie keine darüber hinaus reichenden Aufgaben erfüllen können wie z. B. LaTeX. Dabei handelt es sich um eine Auszeichnungssprache (Markup-Language) wie auch HTML. Ähnlich speziell sind sogenannte Reguläre Ausdrücke, oft kurz RegEx genannt. Sie dienen allein dem Suchen und Ersetzen von Text. Jeder, der schon einmal mit einer Textverarbeitung gearbeitet hat, kennt den Menüpunkt „Suchen und Ersetzen“. Reguläre Ausdrücke sind aber wesentlich leistungsfähiger.
Ich gebe mal ein Beispiel von mir. Ich habe früher Bücher eingescannt, mit einem OCR-Programm bearbeitet und daraus mit LaTeX quasi E-Books erzeugt. OCR steht für „Optical Character Recognition“ und macht aus einem Text-Scan, also einem Seitenfoto, wieder Text, der bearbeitet werden kann. Nun haben Bücher ja ihren eigenen Satzspiegel und damit auch Worttrennungen. Das Minus-Zeichen steht für verschiedene Zeichen: als Minus, Binde-, Trenn- und Gedankenstrich, die sich im Druck unterscheiden. Mich interesssierten nur die Trennstriche am Ende einer Zeile, nicht aber andere Zeichen. Mit Regulären Ausdrücken konnte ich dann eine zielgenaue Suche starten: Ein Minuszeichen, das am Ende einer Zeile steht und dem ein Zeilenumbruch folgte. So habe ich dann die Worttrennungen beseitigt und konnte dem Text ein eigenes Format geben.
Programmiersprachen dienen also dazu, dem Rechner genau zu sagen, was er tun soll. Sie verfügen in der Regel über eine syntaktische Prüfung des Quelltextes. Logische Fehler im Programmaufbau werden dadurch nicht abgedeckt, da gilt der alte Spruch „Garbage in, Garbage out“ oder flapsig formuliert: Wenn ich Mist baue, bekomme ich auch nur Mist.
Mich interessieren Programmiersprachen hauptsächlich wegen ihres „Flavours“. Ich möchte die unterschiedlichen Ansätze in verschiedenen Sprachen sehen. Ich bin weit davon entfernt, ein guter Programmierer zu sein, es interessiert mich auch nicht. Es gibt für fast jedes Problem Programme, die darauf abgestimmt sind. Es gibt in der Bewegung für freie Software einen treffenden Satz: Wir stehen auf den Schultern von Riesen, d. h. wir profitieren von der Vorarbeit früherer Generationen von Programmierern.