Augenkrebs-Warnung!
Das Programmieren erfolgt seit vielen Jahren fast nie auf Papier. Dennoch verwenden die populärsten Programmiersprachen ausschließlich die Textform. Diese funktioniert auch in der Regel. (Bestes Beispiel: Rust) Nur selten gibt es Probleme mit der Textform. Aber da ich ein Perfektionist bin, möchte ich einen radikalen Schritt wagen. Es ist der gleiche, der auch in vielen anderen Gebieten gemacht wurde, beispielsweise in Textverarbeitung, grafischer Modellierung, Audio-Komposition und Dateiverwaltung: Der Schritt von einer Text-basierten Bedienung zu einer grafischen Bedienung. Das hat aber auch den großen Vorteil, für Neueinsteiger viel einfacher erlernbar zu sein! (Vergleich: Scratch)
Optisch soll diese grafische Programmierung auf den verschiedenen Plattformen gleich aussehen. Die Bedienung erfolgt über Maus, Berührung und Tastatur. Das meiste Umdenken fordert der Unterschied, dass das Programm nicht nur aus den Informationen besteht, die man auch auf einen Blick sieht.
Beispielsweise werden Objektidentifikatoren (Pfadangaben) gekürzt angezeigt. Statt dem Verwenden von Variablennamen wird sich per interaktiver Auswahl auf ein Objekt bezogen. Ein freier Platz, beispielsweise ein Funktionsparameter, wird angeklickt und dann kann entweder (falls doch einer vergeben wurde) ein Name eingegeben oder einfach auf den einzufügenden Speicherplatz oder Wert geklickt werden. Variablennamen müssen nicht mehr bestimmten Gesetzen folgen. Sie dienen ausschließlich der Identifizierung für den Programmierer. Intern werden Speicherplätze über IDs identifiziert und referenziert.
Um den Überblick zu verbessern, habe ich einen Farbkodex festgelegt:
package
function
if
, else
switch
, match
loop
, while
, for
{}
, Sprungmarke123
boolean
, true
enum
struct
, Objekttrait
array
, map
, set
type
Natürlich ist die Farbgebung nicht gottgegeben. Ich habe sie mir nach Intuition ausgedacht. Sollte jemand einen besseren Vorschlag haben, bin ich dafür offen.
So in etwa sieht ein kleines Programm in der Entwicklungsumgebung aus:
Hello, World!
1
2
[...]10
Alle Programme befinden sich in Modulen. Ein Modul ist immer eine Klasse, die Merkmale implementieren kann. Das hier gezeigte Modul Test
implementiert das Merkmal :app
, das dem Modul ermöglicht, als Benutzer-Anwendung aufgerufen zu werden. Das Merkmal ist alleine schon dadurch erfüllt, die Merkmals-Methode :start
zu implementieren. Dass es sich bei :start
um eine Merkmals-Methode handelt, lässt sich schnell daran erkennen, dass der Name gelb ist. Und es lässt sich daran erkennen, dass dem Namen in der Funktions-Definition ein :
vorangestellt ist. Die volle Bezeichnung wäre beispielsweise system:app:start
und diese wird dann angezeigt, sobald entweder der Mauszeiger auf den Funktionskopf zeigt oder wenn mit der Tastatur der Fokus auf ihn verschoben wird.
Der :start
-Funktion zugehörig sind zwei Anweisungen:
system:app:terminal:writeLn
aufgerufen. Da diese Funktion nur einen einzigen Parameter erwartet, wird dieser (vom Typen types:string
direkt hinter dem Funktionsaufruf angegeben. Zeichenketten sind genau betrachtet ein Feld an Zeichen, die als Nummer gespeichert werden. Deswegen sind die Zeichen als Zahlen kodiert und die Kette als ganzes kodiert als ein Feld.Der findige Programmierer wird fragen, auf welche :app
-Implementierung sich :writeLn
bezieht. Entgegen der meisten Sprachen gibt es hier keine statischen Variablen oder Objekte. Höchstens Klassendefinitionen sind statisch, also global einmalig gültig. Beim Erstellen der Anweisung wurde die :app
-Instanz auf die Modulklasse automatisch festgelegt. Bewegt man den Mauszeiger auf den Funktions-Aufruf, steht dort module:terminal:writeLn
. Wollte man sich auf eine andere :app
-Instanz beziehen, hätte man das bei der Funktions-Aufrufs-Definition angeben können und dann würde das auch so angezeigt werden.
for...in
-Schleife. Zur Übersichtlichkeit habe ich dem Index den Namen i
gegeben. Da der Speicherplatz i
eine Zahl ist, ist er entsprechend gefärbt. Die Schleife läuft über die Liste der Zahlen von 1
bis 10
. Ähnlich wie in Rust hat [n...m]
das Merkmal, durchlaufbar zu sein. Durchlaufbare Objekte haben die Färbung der Felder.Bei jedem Schritt der Schleife wird zuerst die :toString
-Funktion des zugehörigen Merkmales Anzeigbar der Zahl-Klasse aufgerufen. Als Parameter wird ihr i
übergeben. Die dadurch generierte Zeichenkette wird dann wiederum :writeLn
-Funktion übergeben.
Jedem, der bis hierhin gelesen hat, sollte klar sein, dass die oben gezeigte strukturelle Darstellung eine große Ähnlichkeit mit bestehenden Sprachen aufweist. Dies ist bewusst so gewählt, um den Umstieg zu erleichtern. Funktionen lassen sich aber auch statt als Liste an Anweisungen (Zeitverlauf) als Netzwerk anzeigen (Informationsverlauf). Vereinfacht betrachtet werden die Aufrufe jeweils umgedreht. Bei der obigen Notation wird zuerst das Ziel (die Konsolenausgabe) und dann die Quelle ("Hello, World!") angegeben. Variablen werden dann zu Rohren bzw. Knoten, falls ein Wert mehrmals verwendet wird. Aber mit dem Vorteil des einfacheren Informationsflusses kommt auch ein Nachteil: Die zeitliche Abhängigkeit muss nun irgendwie anders geregelt werden. Klassische Prozeduren lassen sich nur mit besonderen Zeitmarkierungen (Ereignisse) gleich effizient realisieren. Der intern erzeugte Graph ist der selbe. Und er ließe sich auf vielfältige Art darstellen. Als erstes möchte ich eine Rasterorientierte Darstellung, orientiert an LogiSim und Factorio. Programme sollten dann hauptsächlich so entwickelt werden (macht auch mehr Spaß), aber man kann es sich am Ende aussuchen. Der wichtigste Schritt ist eben, ein Programm nicht als Zeichenkette, sondern als Graphen zu betrachten, speichern und zu verarbeiten. Es soll keine Redundanz geben und so abstrakt wie möglich geschrieben werden. Nicht jeder kann das so gut. Wie man sein eigenes Programm entwickelt, muss man selbst entscheiden. Nur globale, wiederverwendbare Module müssen sehr redundanzarm und universell sein. Die Vielfalt bei Spielen soll erhalten bleiben, alles andere soll einheitlich sein.
Funktionsparameter werden nicht kopiert, sondern nur geborgt. Um sicherzustellen, dass keine zeitlichen Konflikte auftreten, kann ein Speicherplatz immer nur von einer Funktion zur Zeit verwaltet werden. Gelesen werden darf parallel. Auch dieses Konzept ist mit dem von Rust nahezu identisch. Es werden alle Prozess-Schritte dadurch geordnet, von welchen Daten sie abhängen. Sie werden aber nicht nur dann ausgeführt, wenn alle Parameter vorliegen, sondern immer schon so weit, wie mit den bereits vorliegenden Parametern bereits möglich ist. Auch klassische prozeduale Strukturelemente wie die Reihenfolge von Aufrufen, Schleifen und konditionaler Ausführung werden durch diese Abhängigkeits-Verwaltung realisiert. Zwei Funktionen können auch direkt nebeneinander gekoppelt werden, indem jeweils ein Ausgang mit jeweils einem Eingang verbunden wird. In klassischen Sprachen wäre dies vollkommen unmöglich. Dies geht aber nur, wenn klar ist, dass die Werte immer in der gewählten Reihenfolge vorliegen können, was Einfrieren verhindern soll.
Alle Dinge sind Objekte. Alles, was man im Editor erstellen und verändern kann, sind Objekte. Objekte können einen Wert repräsentieren. Werte können aber auch hergeleitet werden, ohne direkt von einem Objekt zu stammen. Objekte bestehen sowohl zur Entwurfszeit als auch zur Laufzeit, es gibt keine feste Grenze dazwischen. Objekte sind also im Grunde wie Begriffe/Wörter für Menschen; sie sind Knoten, an die sich Eigenschaften hängen lassen. Alle manuell erstellten Objekte können benannt werden, um sie direkt mit dem jeweiligen Begriff zu verknüpfen.
Eine Eigenschaft hängt an Objekten, wird aber nur für deren Klasse oder eines ihrer Merkmale bestimmt. Eine Eigenschaft wird für ein Objekt mit einem Wert verbunden. Zum Beispiel die Eigenschaft "Alter", die einen Menschen mit der Zeitspanne "20 Jahre" verknüpft.
Klassen halt. Eine Klasse erweitert eine Klasse um bestimmte Besonderheiten, die auf ihre Instanzen übertragen werden. Instanzen sind Objekte, die deren Merkmale von ihrer Klasse übernehmen. Das Konzept hier noch weiter zu erklären, würde nicht viel bringen. Wenn man einen Menschen als Instanz betrachtet, dann ist "der Mensch" eine seiner Klassen. Er kann nämlich auch eine Instanz der Klasse "der schlaue Mensch" sein, was ihn automatisch zu einen Menschen macht.
Ein Merkmal ist eine Ansammlung an Eigenschaften, die man jeder Klasse hinzufügen kann. Dieses Konstrukt ist deswegen notwendig, da nicht alle Eigenschaften nur "von oben" kommen. Besonders wenn es darum geht, was man mit etwas machen kann und wie das zu tun ist, bringt die reine Klassenhierarchie wenig. Man kann zum Beispiel mit einem Menschen reden, jedoch aber auch mit einem Smartphone. Diese beiden entstammen zum Beispiel erst der Klasse der Gegenstände, jedoch kann man nicht mit jedem Gegenstand reden. Hier wäre dann das Merkmal "Beredbar" sinnvoll.
Ein Typ kann entweder eine Klasse oder ein Merkmal sein. Typen werden zum Beispiel für Eigenschaften verwendet, um anzugeben, was man ihnen zuweisen darf. Intern werden Typen durch das Merkmal "Klassisch" dargestellt. Klassisch ist alles, was sich Klassen-ähnlich verhält, also Merkmale für Instanzen repräsentiert. Dies sind soweit nur Klassen und Merkmale.
Dies sind jene Klassen, die in der echten Welt ein Wort wie "Ja", "Nein" oder ein Adjektiv darstellen. Oder Mengenangaben, Aufzählungen und dergleichen mehr. So gibt es die Klassen der Zahlen, Wahrheitswerte (Ja oder Nein), Mengen, Listen, Zuordnungstabellen und Auswahlwerte.
Mengen sind Objekte, in die sich programmatisch andere Objekte reinpacken lassen. Die Objekte die hineinkommen, entsprechen immer einer nähesten gemeinsamen Klasse, notfalls der Klasse "Objekt", was bedeutet, dass man ALLES dort hinein tun könnte. Wäre aber etwas blödsinnig.
Listen sind eine Untermenge der Mengen, also ist die Liste eine Kindklasse der Menge, also ist jede Liste auch eine Menge. Bei Listen kommt gegenüber der Menge hinzu, dass sie eine Ordnung haben, sich deren Inhalt also in einer bestimmten Reihenfolge auflisten lässt. Text ist auch eine Liste, nämlich aus Zeichen.
Ein umständliches Wort. Im Englischen einfach nur "Map" genannt. Die machen es sich leicht, die Amerikaner. Sie sind Mengen, deren Einträge sich jedoch über sogenannte Schlüssel aufrufen lassen. Bei einem Dateiordner sind die Schlüssel die Dateinamen und die Dateiinhalte die Werte.
Bis hierhin lesen nur Nörds. Also zur Belohnung viel Spaß mit dem aktuellen Stand: Prog