numerix hat geschrieben:Beispiel: Das Implementieren eines einfachen Zählers, der ermittelt, wie oft eine Schleife durchlaufen wurde, stellt für viele Schüler dieses Alters eine große Hürde dar, auch wenn die dafür benötigten Sprachelemente längst vertraut sind. Der Grund ist der, dass der "unvorbelastete" Mensch eben anders zählt als man es in einer imperativen Programmiersprache implementiert.
"Nimm einen leeren Eimer. Bei jedem Durchlauf kippe den Eimer aus, füge einen weiteren Klotz mit dazu und stecke alles wieder in den Eimer hinein. Wenn die Schleife fertig ist, kippe den Eimer aus." Das ist überhaupt nicht offensichtlich und es gibt Schüler, die das niemals wirklich verstehen - sie können es zwar als typisches Programmierelement auswendig lernen und einsetzen, aber es bleibt immer irgendwie suspekt.
Das wäre ein Argument für Lisp oder Prolog als erste Sprache. Da macht man das mittels Rekursion, "der einzig wahren Schleife" (sma).
Als ich das erste Mal vor dem QBasic Interpreter von DOS 5 selig gesessen bin und rauskriegen wollte, wie Programmieren geht, hab ich mit Ausdrücken wie X = X + 1 nicht wirklich was anfangen können, weil mir nicht klar werden wollte, wie es eine Zahl X geben soll, die gleich sie selbst plus eins ist. Auch, dass X + 1 = X in BASIC nicht dasselbe ist wie X = X + 1 fand ich verwirrend. Prozedurale Programmiersprachen sind eben mehr oder weniger hohe Abstraktionen von Computern mit veränderlichen Speicherzellen, während deklarative Sprachen ausführbare Mathematik oder Logik sind. Letzteres liegt zumindest mir viel mehr, und das, obwohl ich in der Schule ziemlich schlecht in Mathe war. Jetzt, wo ich erwachsen bin, und Programmierer, kann ich das ungestraft auf meine Lehrer schieben.
Apropos Lehrer. Ich kann mich gut erinnern, wie mein Mathe-Lehrer Variablen erklärt hat, "das sind veränderliche Zahlen". Was soll das sein, eine veränderliche Zahl? Ist das eine 17, die manchmal auch eine 23 oder ein 749 ist? Der Witz ist: in der Mathematik sind Variablen überhaupt keine Zahlen, sondern Platzhalter für bestimmte Terme in anderen Termen. Es gibt Regeln für den Umgang mit solchen Platzhaltern und Termen, und das Funktionieren dieser Regeln hängt geradezu davon ab, dass Zahlen selbst unveränderlich sind. Currying und der ganze Lambda-Kalkül hängen daran, dass Variablen Platzhalter für Terme sind, und dass eine zwei nicht manchmal auch eine drei ist. Auch, was mathematische Funktionen sind, ist mir erst völlig klar geworden, nachdem ich Freges "Funktion, Begriff, Bedeutung" und "Die Grundlagen der Arithmetik" gelesen hatte. Ich finde das bemerkenswert, dass ich in der Schule das Rechnen mit Formeln gelernt habe (eher schlecht als recht), ohne dass mir beigebracht wurde, was die Grundlagen dieses Rechnens überhaupt sind. Ich habe diejenigen meiner Freunde befragt, die in der Schule Mathe-Cracks waren, ob sie mir erklären können, was Zahlen, Variablen und Funktionen sind, und alle konnten mir bestenfalls eine operationale Erklärung liefern, also: "mit Funktionen kann man ... machen", statt: "Funktionen sind ...". Mit einem von ihnen, einem ziemlich schlauen Programmierer, hatte ich eine Diskussion über Intelligenztest. Mein Argument war, dass bei den typischen "was ist die nächste Zahl dieser Folge"-Fragen jede beliebige Zahl richtig ist, weil es zu jeder Zahlenfolge beliebig viele Funktionen gibt, die, angewendet auf die Folge der natürlichen Zahlen, ebendiese Zahlenfolge erzeugen. Ihn hat das nicht überzeugt, weil "du weißt ja gar nicht, ob das eine Funktion ist". Da war ich doch ratlos. Zahlenfolgen sind eindeutige Abbildungen von den Indices einer Folge auf deren Elemente, und eindeutige Abbildungen nennt man Funktionen. Offensichtlich hat die Frage, was mathematische Funktionen sind, weder in seinem Schulunterricht, noch in seinem Flugzeugbauerstudium, noch in seiner Karriere als (exzellenter) Software-Entwickler je eine Rolle gespielt. Genauso formale Logik. Die gab es in der Schule überhaupt nicht, ist aber IMO notwendig für die Software-Entwicklung. Dass "Wenn es regnet, dann ist die Straße nass" dasselbe bedeutet wie "wenn die Straße nicht nass ist, dann regnet es nicht" liegt daran, dass beide Sätze in Kontraposition zueinander stehen: (p -> q) = (~q -> ~p). Jeder Programmierer wendet solcherlei Gesetze tagtäglich an, aber die wenigsten haben sich je bewusst damit beschäftigt. Geschweige denn mit Lambda-Kalkül, Herbrand-Universum, konjunktiver Normalform (Prolog-Klauseln sind in konjunktiver Normalform), Skolemisierung, überhaupt Prädikaten-Logik. Im Informatik-Studium muss man sie wohl lernen, höre ich, aber keiner meiner Bekannten, die Informatik studiert haben, hat je etwas zB. von Existenz-Einsetzung mitbekommen. Oder der Begriff des Typs: Das ist ein Begriff der AFAIK zum ersten mal bei Russell und Whitehead in den principa mathematica aufgetaucht ist, wo er mehr Probleme gebracht hat, als er gelöst hat. Genauso wie in den Programmiersprachen. Es gibt, ebenfalls AFAIK, keine Programmiersprache außer Haskell und Lisp, die nicht ein kaputtes Typ-System hat. Pythons Typ-System ist nur sehr wenig kaputt (weil Variablen nicht typisiert sind), und unglaublich flexibel, was einer der Gründe dafür ist, dass ich Python so mag. Das Typ-System von Java fühlt sich für mich an wie ein Betonklotz am meinem Bein und ich mit
curly braces gefesselt am Grunde der Isar. Das von C++ ist ein Irrgarten aus dem es kein Entrinnen gibt. Das von Haskell basiert auf dem typisierten Lambda-Kalkül, weswegen es eben auch nicht kaputt ist.
Während ich so schreibe, kommt mir die Vermutung, dass dieser längliche rant eigentlich auf etwas anderes, grundlegenderes, abzielt, was mich schon seit längerer Zeit wurmt. Die fehlenden analytischen Grundlagen in der Programmieren-als-Beruf-Ausbildung resultieren, so meine These, aus dem Verständnis heraus, dass Programmieren eine technische Beschäftigung ist. Wir sind Programmierer, wir sind Techniker, wir gehen mit Technik und Technologie um. Die Mathematik, die wir brauchen, ist Ingenieurs-Mathematik, Analysis++ und Statistik++. Alle fachlichen Probleme sind technische Probleme. Wir ziehen uns unseren Blaumann an und krempeln die Ärmel hoch. Wenn die Maschine irgendwo hakt, nehmen wir einen Hammer oder Schraubenzieher und werkeln dran rum bis sie wieder läuft. Da gibt es dann diejenigen, die keiner Schraube trauen, deren Gewinde sie nicht selbst geschnitten haben (
Not-Invented-Here-Syndrom), oder am anderen Ende des Spektrums diejenigen, die nur mit standardisierten industriell gefertigten Werkzeugen arbeiten wollen (ASP.net, J2EE, ...). Alle 1,5 Jahre kommt eine neue Art von Werkzeugen, deren Verwendung wir lernen sollen, und der Programmierer wird immer mehr zum Bedien-Spezialisten neuester oder auch nicht ganz so neuer technologischer Werkzeuge. Die verwendete Sprache ist dabei bloß das Medium, in das man seine Gedanken kodiert, und zwar transformiert in den Kategorien der jeweiligen Technologie, von wo aus der Computer sie dann wieder dekodiert und schließlich ausführt. Sprache ist aber gar kein Medium. Wenn es
ein Resultat der Analytischen Philosophie in hundert Jahren gegeben hat, dann das, dass Sprache kein Medium zur Gedanken-Übertragung ist, sei es an andere Menschen oder Computer. Ein gesprochener Satz ist nicht bloß eine Kodierung eines Gedankens in Schallwellen, den mein Gegenüber nach dem Hören wieder dekodiert, um an den Informationsgehalt zu gelangen. Die Sprache ist nicht in diesem Sinne transparent, sondern opaque. Ihre Ausdruck-Möglichkeiten bestimmen, wie wir denken, und was wir überhaupt sagen können. Beim Programmieren doch genauso: wir versuchen beständig unerhörte neue Dinge in die Welt zu setzen, aber die verwendete Programmiersprache bestimmt die Kategorien in denen wir denken und die Abstraktions-Ebenen, zu denen wir hinaufgelangen können.
Je mächtiger und ausdruckstärker die Sprache, um so besser. Oft hilft einem die mächtigere Programmiersprache nicht mal. Mein letzter Gig war ein Python-Job, aber es war furchtbar. Man konnte jedem Code ansehen, wer ihn geschrieben hatte. Er schrie einen förmlich an "Java!", "Dephi!". Auch hier die - für mich absurde - Vorstellung, Programmieren sei immer die gleiche Tätigkeit, auf demselben Abstraktions-Niveau (lies: Arrays und for-Schleifen, jede Funktion ist eine Methode "weil das ist objektorientiert"), nur die Technologie variiert. Python ist aber kein Java mit weniger Klammern, sondern IMO eher ein Lisp mit weniger Klammern. Wenn man solch reiche Ausdrucks-Möglichkeiten hat wie in Python, dann ist man bescheuert, wenn man sie nicht ausnutzt, das endet sonst mEn. immer in Programmen von byzantinischer Formelhaftigkeit (AKA boiler plate code) und barocker Verschnörkelung, in der vier- bis zehnfachen Länge, die eigentlich nötig wären, dasselbe in idiomatischem Python elegant zu formulieren. Wenn man dann was sagt erntet man bloß komische Blicke. Weil es funktioniert ja. Der Computer tut ja, was er soll. Aber "Programs must be written for people to read, and only incidentally for machines to execute." - H. Abelson and G. Sussman (in 'The Structure and Interpretation of Computer Programs'). Wenn ein Programm schon aussieht, als wäre die Katze über die Tastatur gelaufen, dann will ich mich gar nicht mehr damit beschäftigen müssen (Mein Lieblings-Beispiel war eine Zeile, die außer der notwendigen Einrückung keine spaces enthielt, die aber trozdem 208 Zeichen breit war). In einer ausufernden Implementation die implementierten abstrakten Konzepte zu suchen, ist extrem mühselig. Warum sehe ich so wenige Programmierer, die,
wie Paul Graham vorschlägt, bottom up immer neue Abstraktionen entwickeln, in denen sie dann ihr Programm formulieren? Ja, ich weiß, alle verwenden Klassen und virtuelle Methoden-Aufrufe, aber man könnte doch, gerade in Python, oft eine interne DSL bauen, mittels der sich dann eine bestimmte Problem-Klasse leicht beschreiben und lösen lässt.
pyparsing wäre ein Beispiel. Was ich statt dessen zu sehen bekomme, ist ein minimales Basisklassengerüst als Framework, resultierend in Funktionen von mehreren hundert Zeilen Länge, weil alles Abstraktions-Vermögen top down gerichtet war und schon verbraucht wurde. Irgendwo muss man ja stehenbleiben, wenn man von oben nach unten designt, und alles, was noch weiter unten ist, sind eben besagte Arrays und for-Schleifen, und Variablen als Daten-Töpfchen zum zwischen- oder Endlagern von Ergebnissen.
Überhaupt top-down-Analysen: wenn wir als Programmierer ein Programm schreiben, dann analysieren wir zwar zuerst das praktische Problem, das durch das Programm gelöst werden soll, und dazu verfertigen wir eine wie auch immer geartete Beschreibung des Problems. Die Kunst besteht aber darin, eine kurze und elegante Beschreibung des Problems zu finden, die dann eine ebenso kurze und elegante Lösung nach sich zieht. Und zwar auf allen Ebenen. Die Mittel der Wahl dazu sind Abtraktion und Generalisierung. Meine These ist jedenfalls, dass die Kunst der Programmierung zunächst mal darin besteht, die richtigen Abstraktionen und Generalisierungen für jede Ebene zu finden. Wir sehen das Gegenteil doch täglich hier im Forum bei den Anfängern. Neben den typischen Problemen, dass sie die Standard Lib und die Syntax nicht kennen, sehen deren Programme aus wie Kraut und Rüben, weil sie nicht abstrahieren und aller Code sich auf genau einer Ebene bewegt, gerne auch mit GUI und multi threaded. Alles ist bis zum Erbrechen vollgestopft mit ad hoc
if-Schleifen, und der Zustand des Programms ist verteilt auf unzählige globale Variablen, weil sie noch nicht gelernt haben, Zustand einzukapseln und ihn nur mittels - ggf. polymorpher - Operationen kontrolliert und unter Einhaltung notwendiger Invarianzen zu ändern, damit nachher wieder ein neuer konsistenter Zustand besteht. Die Zauberworte hier sind "Zustand" und "polymorph", womit eigentlich schon die ganze OO beschrieben wäre. Wo dabei welches Rädchen und Schräubchen hinkommt und auf welche Abstraktions-Ebene, das muss man lernen, aber das ist nichts, was einem Technologie oder Frameworks abnehmen könnten. Da komme ich nun doch wieder zum Ausgangspunkt zurück, dem Informatik-Unterricht. Ich fände es begrüßenswert, wenn man den Kindern in der Schule und in der Hochschule vermitteln könnte, dass der Kern der Programmierung nicht technisch ist, sondern analytisch, und wenn man ihnen geistige Werkzeuge an die Hand geben könnte, mit denen sie bessere Problem-Beschreibungen und a fortiori auch bessere Problem-Lösungen erzeugen können. Wie man das macht weiß ich allerdings auch nicht, da ich kein Didaktiker bin. Aber Python als erste Programmiersprache ist schon mal ein guter Anfang.