Skriptsprachen – wenn Typen (scheinbar) unwichtig werden
Wir haben uns bis jetzt mit den wichtigsten kompilierten Programmiersprachen C, C++, C# und Java auseinandergesetzt und die Skriptsprachen erstmal links liegen gelassen. Der Grund hierfür ist, dass wir uns um die innere Verarbeitung gekümmert haben, was uns oft zu Speicherfragen geführt hat. Die meisten Skriptsprachen werden aber oft als „nicht typisiert“ bezeichnet, was aussagen soll, dass sie aus Sicht des Programmierers vordergründig keine Datentypen kennen. Dies ist vorerst mal überraschend (und eigentlich auch falsch), da am Ende des Tages der Rechner die Daten ja irgendwie im Speicher ablegen muss und dort entscheiden muss, wie viel Platz für die Variablen benötigt werden. In Wirklichkeit kennen die Skriptsprachen sehr wohl Datentypen, sie verbergen sie nur vor dem Programmierer. Insofern sollte man richtigerweise von „dynamischer Typisierung“ sprechen – die Typprüfung und Typfestlegung wird also nicht beim Kompilieren, sondern bei der Ausführung durchgeführt. Dies ist für die Codeerstellung sehr praktisch und reduziert die Entwicklungszeit. Jedoch „zahlt“ man dies durch langsamere Ausführungszeiten. Wenn wir mal davon ausgehen, dass wir keinen hoch performanten Code schreiben wollen, ist in den allermeisten Fällen dieses „Verbergen“ der Datentypen auch sehr praktisch – in einigen wenigen Situationen jedoch ist es recht frustrierend, da wir die interne Datenhaltung erahnen müssen, um für bestimmte Situationen Vorkehrungen für Fehlerbehandlungen vorzusehen. Da wir aber jetzt durch die Erkenntnisse der kompilierten Programmiersprachen die internen Notwendigkeiten verstehen, können wir diese Vorkehrungen auch besser umsetzen.
Top
Die wichtigsten Skriptsprachen
Bevor ich in die wesentlichen Elemente von Skriptsprachen einsteige, noch ein paar allgemeine Worte vorweg. Da es sehr viele unterschiedliche Skriptsprachen gibt, muss ich mich hier auf die wichtigsten konzentrieren. Die vermutlich am meisten genutzte Skriptsprache ist JavaScript, gefolgt von PHP bzw. immer häufiger Python. Jede dieser drei Sprachen hat seinen primären Anwendungsbereich.
JavaScript ist die defacto Skriptsprache für Web Frontends – sprich interaktive Webseiten. Jeder Browser kann JavaScript interpretieren, was die extrem weite Verbreitung von JavaScript erklärt. Somit gibt es eine sehr breite Community an JavaScript Entwicklern. Basierend auf diesem Erfolg hat man begonnen, mit node.js das Backend – sprich, die Server – auch dazu zu bringen, JavaScript zu verstehen. Oftmals wird Typescript im gleichen Atemzug wie JavaScript genannt, wobei TypeScript primär ein vermeintliches Manko von JavaScript adressieren soll – die fehlende Typisierung. Typescript wird jedoch von einem Compiler einfach „nur“ zu JavaScript umgewandelt, welches dann am Ende ausgeführt wird. Dies birgt Vor- und Nachteile. Vorteilhaft ist neben der Typisierung, dass man beim Compilevorgang die Zielversion von JavaScript einstellen kann und eine gewisse Flexibilität bezüglich des Supports der Zielarchitekturen erreichen kann. Wenn bspw. die Software auf einem relativ alten Browser laufen soll, stellt man den Compiler auf diese Version ein. Nachteilig ist, dass man einen weiteren Entwicklungsschritt berücksichtigen muss. Eventuell wird man anstatt JavaScript auch ECMA Script (European Computer Manufacturers Association) oder kurz ES finden. Die ECMA versucht einen gemeinsamen Sprachkern zu definieren um zu verhindern, dass einzelne Browserhersteller Funktionalitäten ins Leben rufen, welche von anderen wiederum nicht sauber interpretiert werden können, was dem offenen Gedanken des Internets wiedersprechen würde. Ich werde mich hier auf verschiedene Konzepte von JavaScript konzentrieren, welche bis inklusive ES6 (also ECMA Script Version 6 – seit 2015) unterstützt werden.
PHP ist ebenfalls sehr verbreitet, da es eine sehr effektive Sprache für die serverseitige Programmierung von Webapplikationen ist – und mit node.js, also JavaScript, eine mächtige Konkurrenz bekommen hat. PHP wurde explizit für den Anwendungsfall der serverseitigen Webverarbeitung entwickelt. PHP ist zwar objektorientiert – die Ursprünge von PHP kommen nicht aus der Objektorientierung, welche erst später hinzugefügt wurde. Dadurch finden wir in PHP oftmals nicht ganz stringent umgesetzte objektorientierte Ansätze.
Python steigt stetig in der Beliebtheit und ist mit dem Ziel entwickelt worden, möglichst sauberen und modernen Code zu forcieren, was unter anderem die weite Verbreitung in Hochschulen erklärt. Python wurde auch in diversen Programmen für die Makroprogrammierung eingebaut, bspw. Blender oder OpenOffice. Derzeit ist Python für das maschinelle Lernen die Programmiersprache der Wahl, da die wichtigsten Bibliotheken hierfür (Keras bzw. jetzt TensorFlow) in Python geschrieben wurden – bzw. korrekterweise muss man sagen, dass die Routinen in C/C++ geschrieben wurden und als Pythonmodul zur Verfügung stehen – sprich wir nutzen diese Bibliotheken in Python. Dies ist auch einer der Stärken von Python – es ist relativ einfach möglich, eigene C/C++ Programme zu schreiben und sie innerhalb Python zur Verfügung zu stellen(1), sprich wir können Python erweitern. Dies erklärt auch, warum Python trotz der relativ schlechten Performance für so ressourcenintensive Themen wie KI (Künstliche Intelligenz) eingesetzt wird. Python fungiert hier „nur“ als Rahmenwerk für den Aufruf externer Module. Ein weiterer Ort, an dem wir Python oft finden, ist der Raspberry PI (bei dem „PI“ übrigens für Python Interpreter steht). Es ist relativ einfach im Raspberry PI die Systemressourcen und Funktionen mit Hilfe von Python Skripten zu erreichen. Um vorab noch Unklarheiten bezüglich der Benamung auszuräumen. In den Onlinedokumentationen findet man häufiger den Begriff „CPython“. Hierunter versteht man „die“ Pythonimplementierung – es ist also gleichbedeutend mit „Python“. Nun hat man Python auch in anderen Programmiersrpachen realisiert – bspw. wurde Python in Java „nachgebaut“ – und man ahnt es bereits, dies nennt man JPython. Die Referenzimplementierung wurde aber in C geschrieben, weshalb man standardmäßig immer von CPython ausgeht.
Fangen wir nun erstmal mit der guten Nachricht an. Genau wie bei den kompilierten Sprachen finden wir bei den Skriptsprachen viele Konzepte wieder. Man muss nicht alles neu lernen. Die grundsätzlichen Konstrukte einer Programmiersprache finden wir in C, C++, C# und Java genauso wie in JavaScript, PHP und Pyhton – nur eben mit Ausnahme der Typisierung. Insofern müssen wir uns lediglich mit kleineren Detailfragen beschäftigen – wie werden Variablen deklariert und warum können beispielsweise Arrays dynamisch vergrößert werden und so weiter. Da alle drei hier referenzierten Skriptsprachen objektorientiert genutzt werden können, werde ich die Detailfragen zu Objekten und Klassen im Abschnitt zur Objektorientierung nachliefern. Weiterhin gilt es zu erwähnen, dass der fehlende Compiler die Arbeit als Programmierer mit Skriptsrpachen an manchen Stellen eher erschwert als erleichtert. Wenn wir ohne einer speziell auf die relevante Skriptsprache angespasste IDE arbeiten, dann werden Fehler oft erst zur Laufzeit bemerkt – was sehr unbefriedigend sein kann. Insofern wird empfohlen, auf jeden Fall mit einer passenden IDE mit Code Highlightning(2) zu arbeiten, um zumindest strukturelle Syntaxfehler bereits beim Schreiben der Software zu bemerken.
(2) Das automatische optische Hervorheben von unterschiedlichen Codefragmenten und Anzeigen möglicher Fehler
Beginnen wir mit dem Start der einzelnen Applikationen. Hier gilt es wieder den Anwendungsfall im Auge zu behalten. Wenn wir beispielsweise ein PHP-Programm schreiben, dann wird es in aller Regel ein serverseitiges Skript zur Verarbeitung von http Anfragen sein. Dies bedeutet, dass nicht wir das Programm direkt starten, sondern eine Webbrowser Anfrage dies für uns erledigt. Das heißt wiederum, dass wir einen Server benötigen, der als Laufzeitumgebung für unser Skript fungiert. Für JavaScript wiederum haben wir in Kapitel 3 gesehen, dass der Start unserer Routinen über Browserbuttons oder einfach nur dem Laden einer Seite erfolgt. Für unsere Zwecke in diesem Buch reicht es am Anfang jedoch erstmal aus, die Skriptprogramme manuell zu starten, also ohne den Kontext eines Browsers oder Servers. Dies ist zwar eine erhebliche Vereinfachung der Situation; für das Kennenlernen der Verhaltensweisen unseres Computers jedoch absolut ausreichend.
Top
JavaScript
Sehen wir uns das Hello World Programm mal in JavaScript an. Hierzu wollen wir es also nicht über einen Browser starten, sondern über die node.js Laufzeitumgebung:
Listing 1: Hello World in JavaScript
Das vermutlich Auffälligste im Vergleich zu den kompilierten Programmiersprachen ist das Fehlen des Hauptprogramms. Der Interpreter arbeitet das Skript einfach von der ersten Zeile beginnend ab, weshalb unser Hello World Programm auf eine einzige Codezeile zusammenschrumpft. Wir sehen bereits einen Charme der Skriptsprachen – sie versuchen uns das Leben möglichst leicht zu machen. Man hat in interpretierten Sprachen die Möglichkeit, das Skript einfach abzuarbeiten, warum also ein Hauptprogramm einfordern? Aber es gibt leider immer eine Kehrseite der Medaille. Die Frage ist nämlich, können wir wie in den anderen Programmiersprachen nun keine Aufrufparameter hinterlegen? Nun, hierfür haben sich die Programmierer der node.js Laufzeitumgebung eine Lösung überlegt. Es gibt fest definierte Objekte, welche von Haus aus existieren. Eines davon ist das process Objekt, in dem alle für den aktuellen Prozess relevanten Informationen zu finden sind – und somit auch die Aufrufparameter. Sehen wir uns hierfür folgenden Code an:
Listing 2: Hello Parameter in JavaScript
Kurze Erklärung der Codezeilen: Die Ausgabe über „console.log()“ erhält einen zusammengesetzten String, der aus „Hello “, einem Parameter und dann einem Ausrufezeichen besteht. Der Paramter befindet sich im process Objekt und dort in der Eigenschaft „argv“. Dies ist ein Array, welches die Aufrufparameter enthält, wobei die Position 0 für den absoluten Pfad auf node.exe – also der Laufzeitumgebung steht, die Position 1 für den absoluten Pfad auf das aktuelle Skript (in unserem Fall habe ich es myHello.js genannt) und ab der Position 2 auf die Aufrufparameter abgreifbar sind.
Starten wir das Programm mit „node myHello Mars“ in der Konsole und erhalten:
Ein Hinweis noch zum Semikolon am Ende jeder Zeile, da es hier mitunter verschiedene Ansichten dazu gibt, ob es notwendig ist oder nicht. JavaScript kann beim Interpretieren die fehlenden Semikolons automatisch einbauen – hier wurde ein Automatismus etabliert, der dies erledigen soll. Im Zuge von Konsistenz und Kompatibilität empfehle ich aber, die Semikolons immer selbst zu schreiben und dies nicht der ASI (Automatic Semicolon Insertion) zu überlassen.
Top
Python
Sehen wir uns nun das HelloWorld Programm mal in Python an:
Listing 3: Hello Parameter in Python
Kurze Erklärung der Codezeilen: In Python finden wir die Aufrufparameter im Modul „sys“, welches wir zuerst über einen Import bekannt machen müssen. „sys“ besitzt das „argv“ Array, in dem wir auf Position 0 den Skriptnamen finden und ab Position 1 die Parameter.
Was vielleicht auffällt ist das Fehlen der Semikolons am Ende jedes Statements. Die „Erfinder“ von Python versuchten, alles Unnötige aus der Sprache herauszulösen. Das Semikolon dient in den anderen Sprachen ja als Trennzeichen zwischen einzelnen Statements. Nun macht fast jeder Programmierer aber nach dem Semikolon einen Zeilenumbruch, damit das nächste Statement in der nächsten Zeile beginnt und der Code somit übersichtlicher wird. In Python ist dieser optionale Zeilenumbruch nun Pflicht, weshalb das Semikolon obsolet geworden ist. Wir werden im Folgenden noch ähnliche Besonderheiten von Python sehen. Starten wir nun unser kleines Python Programm mit „python myHello.py World
Also auch hier wieder sehr ähnliche Konzepte in den einzelnen Programmiersprachen.
Top
PHP
Nun fehlt uns jetzt noch PHP:
Listing 4: Hello Parameter in PHP
Kurze Erklärung der Codezeilen: PHP benötigt keinen Import für Grundfunktionen wie echo – was eine Ausgabe ermöglicht. Wichtig ist aber die Einleitung von PHP Code mit „<?php“ und die Endmarkierung mit „?>“. Dies zeigt dem Interpreter die Stellen an, welche zu interpretieren sind. Alle anderen Stellen werden 1:1 „durchgereicht“, sprich unverändert ausgegeben. Im Code sehen wir, dass die Ausgabefunktion echo keine Klammern benötigt, sondern alles im Folgenden bis zum Semikolon ausgibt. Die Stringverkettung erfolgt in PHP nicht mit „+“ wie in JavaScript, sondern mit dem Punkt. Ein weiterer Unterschied zu JavaScript und Python ist, dass Variablen in PHP immer mit einem vorangestellten „$“ zu versehen sind, da sie sonst nicht als Variablen erkannt werden. Die Aufrufargumente sind in PHP nicht in einem Objekt versteckt, sondern in einem global existierenden Array namens „$argv“. An der 0. Position finden wir wieder den Namen der ausgeführten Datei (in unserem Fall wäre dies „myHello.php“) und ab der 1. Position die Aufrufparameter.
Bevor wir auf die Markierungen nochmal eingehen, starten wir den Code mit „php myHello.php World“ und erhalten wieder:
Gehen wir nochmal auf die Besonderheit der Anfangs- und Endmarkierungen von PHP ein. Diese Sprache wurde speziell dafür entwickelt, Webinhalte serverseitig zu verarbeiten. Nun versteht unser Browser streng genommen nur in HTML codierte Informationen. Also muss PHP auch HTML codierte Daten an den Browser senden. Es wäre jetzt aber sehr aufwändig, die gesamte HTML Struktur über echo Befehle auszugeben. Viele andere Programmiersprachen wie bspw. Java würden so etwas zwar fordern wenn sie serverseitig für Webinhalte genutzt werden – diese Programmiersprachen wurden aber nicht primär für den Zweck der HTML Datenerzeugung entwickelt, sondern sind nachträglich auf diesen Einsatzzweck „getrimmt“ worden. Bei PHP hat man von Tag 1 an aber diesen Zweck „HTML“ im Fokus gehabt. Stellen wir uns nun vor, wir haben folgende Webseite:
Listing 5: HTML Seite für Hallo Welt Ausgabe
Wir wollen aber das „Welt“ serverseitig austauschen – sagen wir in der Variablen $currentLocation ist der Wohnort des Users gespeichert (wie auch immer dieser Wert da hingekommen ist). Wenn wir diese Seite jetzt als dynamische PHP Seite erstellen wollen, dann würde sie wie folgt aussehen:
Listing 6: PHP Seite für Hallo „Variable“ Ausgabe
Wenn nun der Server dieses File verarbeitet, wird alles bis „<?php“ 1:1 an den Client gesendet. Ab dieser Markierung wird der Code ausgeführt – sprich der Inhalt der Variablen $currentLocation ausgegeben und nach „?>“ wieder alles 1:1 ausgegeben. Für den Bau von Webseiten ist dieses Vorgehen also recht praktisch. Versuchen wir nun von diesem Verhalten für unser „myHello.php“ Programm zu profitieren und bauen es etwas um:
Listing 7: Hello Parameter in PHP mit 1:1 - Durchreichung von Text
Starten wir wieder mit „php myHello.php World“, erhalten wir:
Top
Variablen
Der nächste wichtige Punkt beim Verständnis von Programmiersprachen ist der Umgang mit Variablen, wie wir es bei den kompilierten Sprachen ja bereits untersucht haben. Sehen wir uns folgenden Python Code hierfür mal an:
Listing 8: Variablennutzung in Python
Kurze Erklärung der Codezeilen: Da Python dynamisch typisiert ist, haben die Python Autoren die Kennung von Variablen für die Deklaration komplett entfernt. Wir brauchen also vor stringVar nichts schreiben. In JavaScript müsste hier var oder let stehen, in PHP ein Dollarzeichen. Wie alle dynamisch typisierten Sprachen, muss Python aus dem Kontext heraus entscheiden, um was für ein Datentyp es sich handelt. In unserem Fall haben wir eine Stringkonstante "2" zugewiesen, also weiß Python, es muss sich um einen String handeln. Bei intVar wird eine ganze Zahl zugewiesen, also wird intern auch eine ganzzahlige Variable erstellt. Nun versuchen wir diese beiden Variablen zu vergleichen. Dies würde in C immer zu „ungleich“ führen, bei Java und C# wäre der Vergleich nicht möglich – der Compiler würde den Code nicht kompilieren, da der Vergleich von unterschiedlichen Datentypen wie String und int nicht vorgesehen ist. In dynamisch typisierten Sprachen muss so etwas aber gehen, da wir die Variablen beim Codieren ja noch nicht einem Typen zugeordnet haben. Je nachdem, ob Python nun die beiden Werte als gleich oder ungleich interpretiert, würde die entsprechende Meldung ausgegeben.
Auch hier noch ein kurzer Hinweis auf die Python Besonderheiten. In den meisten Programmiersprachen gilt es als „guter Stil“, abgegrenzte Bereiche (meist solche, die durch geschweifte Klammern abgegrenzt werden, wie bspw. Schleifenrümpfe) auch optisch einzurücken, um den Code lesbarer zu machen – siehe hierzu bspw. die Schleifen im nächsten Kapitel. In Python geht man hier einen Schritt weiter, indem die Einrückung zwingend vorgeschrieben ist, wodurch wiederum die geschweiften Klammern obsolet geworden sind. Da man theoretisch auch auf die Klammern nach „if“ verzichten kann, muss nun ein Zeichen die if Bedingung abschließen, weshalb hier der Doppelpunkt nach dem Vergleich und somit konsequenterweise auch nach dem „else“ eingetragen wird.
Starten wir aber nun das Programm und sehen, wie Python die beiden Variablen interpretiert. Wir erhalten ein „ungleich“ auf dem Bildschirm. Python hat also einen Vergleich durchgeführt und beide Werte als unterschiedlich erkannt – da Python unter einem String etwas anderes versteht als unter einer Zahl.
Ab der Version 3.6 hat Python eine Möglichkeit eingeführt, einen Typenhinweis an eine Variable zu hängen. So kann man den Code auch wie folgt schreiben:
Listing 9: Variablennutzung in Python mit Typenhinweis
Wir schreiben also nach der Variablen einen Doppelpunkt, gefolgt vom Datentyp. Diese Ergänzung hat allerdings nur informativen Charakter. Intern passiert genau das gleiche wie ohne Typisierung. Wir können dies sehr einfach beweisen, indem wir einfach die Anführungsstriche bei „2“ entfernen und erhalten danach ein „gleich“ auf dem Bildschirm. Dieses „flaggen“ von Variablen mit den empfohlenen Datentypen ist lediglich für nachträgliche Prüfungen des Codes wichtig. Insofern werden wir an dieser Stelle nicht weiter darauf eingehen.
Kümmern wir uns nun um JavaScript. Wir schreiben also die gleiche Funktionalität in JavaScript:
Listing 10: Variablennutzung in JavaScript
Kurze Erklärung der Codezeilen: JavaScript ist von der Syntax her sehr nahe an Java und C angelehnt, weshalb die if Abfrage 1:1 wie in diesen Programmiersprachen aussieht. Die Variablendeklaration wird in JavaScript auch benötigt, allerdings nicht mit einem Datentypen, sondern nur mit dem Schlüsselwort var, let oder const, wobei const eine Konstante definiert (der Wert kann also nicht mehr verändert werden). let und var deklarieren eine Variable, wobei var die erneute Deklaration dieser Variablen im aktuellen Kontext erlaubt und let dies verhindert. Es wäre also in unserem Codebeispiel möglich var intVar = 3; am Ende zu schreiben, nicht aber let stringVar = "2";
Wenn wir den Code nun ausführen, sehen wir erstmal eine Überraschung – wir erhalten ein „gleich“. JavaScript scheint also nicht zwischen Strings und int zu unterscheiden? Die Antwort ist nun etwas komplizierter. JavaScript ist dazu gebaut worden, viele Daten über den Webbrowser zu verarbeiten. Die Eingaben in Textfeldern sind immer vom Datentyp „String“. Wenn aber nun Zahlen eingegeben werden, wollte man den Entwicklern die Mühe sparen, die Werte immer in „int“ umzuwandeln – wir erinnern uns an die Notwendigkeit des Parsens von Strings in Java und Co. Dieses Verhalten von JavaScript scheint nun aber dem Gedanken zu widersprechen, dass die Daten in einem Computer grundsätzlich typisiert im Speicher abgelegt werden müssen. Die Klärung dieses Widerspruches ist nun relativ einfach. In JavaScript wird bei einem Vergleich mit „==“ automatisch eine Typkonvertierung gemacht, bevor verglichen wird. Man spricht hier von einer „losen Gleichheit“. Nun gibt es aber Situationen, bei denen man durchaus den Unterschied zwischen dem String „2“ und der Zahl 2 unterscheiden möchte. Dieser ist zwar seltener, aber denkbar. Hierfür hat man in JavaScript die Prüfung auf „strikte Gleichheit“ eingeführt, was mit einem dreifachen Istgleichzeichen notiert wird: „===“. Probieren wir es aus:
Listing 11: Strikte Gleichheit in JavaScript
Führen wir diesen Code aus, erhalten wir ein „ungleich“ auf der Konsole. In PHP ist das Verhalten genauso wie in JavaScript, sprich man unterscheidet die lose und die strikte Gleichheit (hier auch mit doppelten und dreifachen Istgleichzeichen). In Python gibt es das nicht – hier müssen wir für eine lose Gleichheitsprüfung selbst den String in eine Zahl umwandeln:
Listing 12: Variablennutzung in Python
Die Parsefunktion ist in Python int(), wodurch wir hier wieder ein „gleich“ auf der Konsole finden. Ist das Vorgehen von JavaScript und PHP nun sinnvoller, oder problematischer? Die Antwort liegt wieder im Auge des Betrachters – bzw. daran, was man eigentlich erreichen will. Wie weiter oben schon mal erwähnt, sind bei einem Großteil der Anwendungsfälle die „Annehmlichkeiten“ von Skriptsprachen sehr entgegenkommend für den Programmierer. Die restlichen Fälle können dann eher Kopfzerbrechen bereiten und müssen mit „Extralocken“ behandelt werden. Sehen wir uns nun folgenden JavaScript Code hierzu mal an:
Listing 13: Unterschiedliches Datentypverhalten in JavaScript
Obwohl laut Listing 10 die beiden Variablen bei einem losen Vergleich identisch sind, erhalten wir beim „+“ Operator unterschiedliche Ergebnisse, wie die Konsolenausgabe uns Verrät:
Das Problem ist, dass in JavaScript der „+“ Operator bei Strings eine Verkettung durchführt und bei Zahlen eine Addition. Auch wenn also die Skripte uns die Entscheidungen über die Datentypen abnehmen – die tatsächlich genutzten Datentypen sollten wir uns trotzdem immer vor Augen führen. Für Analysen bieten die einzelnen Skriptsprachen oft einen Weg, den Datentypen festzustellen. In JavaScript wäre dies „typeof“:
Listing 14: Ermittlung des Datentyps in JavaScript
Die Konsole verrät uns hier „number“. In PHP wäre diese Funktion „gettype()“ und in Python „type()“. Nun bleibt die Frage, wie bestimmen die Skriptsprachen eigentlich die Datentypen? Im Prinzip haben wir diese Frage schon beantwortet – bei der Zuweisung wird geprüft, was eben zugewiesen wird und der Datentyp des zugewiesenen Wertes wird auf die Variable übertragen. Das funktioniert bei Strings sehr gut, bei Zahlen nur dann, wenn wir nicht bis zu den Grenzwerten stoßen. Bei Gleitkommazahlen greift JavaScript auf „double“ zurück – sprich eine 64 Bit Gleitkommazahl, wie es in Kapitel 6 beschrieben wurde. Intern wird dieser Datentyp zwar „Number“ genannt, ist aber nichts anderes. Komplizierter wird es bei ganzen Zahlen. Hier gilt es drei verschiedene Situationen zu unterscheiden:
  • Zahlen im Bereich von int (also – 2.147.483.648 bis +2.147.483.647).
  • Zahlen im Bereich der Mantissengenauigkeit einer double Zahl plus Vorzeichenbit (+/-253-1, was einen Wertebereich von -9.007.199.254.740.991 bis 9.007.199.254.740.991 ergibt).
  • Zahlen außerhalb des Vorausgenannten.
Es gibt jetzt Operatoren, welche bei den einzelnen Wertebereichen unterschiedlich funktionieren. Beginnen wir mit dem Integerbereich. Hier funktionieren erstmal alle mathematischen Operationen, Vergleiche und die Bitshift Operationen, also das Verschieben der Bits nach links (sprich abschneiden der rechten Bits) oder Verschieben nach rechts (sprich rechts Auffüllen mit 0). Sobald ein Wert größer als der definierte Wertebereich int ist, wird intern der Wert als Gleitkomma gespeichert, aber als Ganzzahlenwert ausgegeben. Dadurch funktionieren noch alle mathematischen Vorgänge, nicht aber das Verschieben von Bits:
Listing 15: Verhalten von ganzzahligen Daten in JavaScript
Kurze Erklärung der Codezeilen: Wir initialisieren intVar mit einem Wert innerhalb der int Grenzen (hier der größte gerade Wert). Danach geben wir den Wert geteilt durch zwei aus. Ein Bitshift nach rechts ist ebenfalls eine Division durch 2, weshalb wir also mit „intVar>>=1“ die Division als Bitshift durchführen und den Wert gleich wieder in intVar eintragen (das wird durch das „=“ nach dem „>>“ garantiert). Nun geben wir diesen Wert aus. Das Gleiche machen wir nun auch mit einem Wert, der gerade außerhalb des int Wertebereiches liegt.
Führen wir das Programm aus, sehen wir:
Die Division ist also der mathematische Prozess und gibt in beiden Fällen den richtigen Wert aus – innerhalb und außerhalb der int Grenzen. Die Bitshift Division liefert aber nur innerhalb der int Grenzen einen korrekten Wert. Die nächste zu untersuchende Grenze ist 253-1. Sehen wir uns hierfür folgenden Code an, in dem wir den maximal möglichen ganzzahligen Wert mit += 1 um eins erhöhen:
Listing 16: Überschreitung des sicheren ganzzahligen Bereiches in JavaScript
Die Ausgabe lautet wieder 9007199254740992. Die Addition um 1 war also außerhalb der Präzision der darunterliegenden Gleitkommazahl. Ändern wir die Erhöhung auf 2 mit += 2 erhalten wir den korrekten Wert 9007199254740994. Über dem im Programm angegebenen Initialisierungswert können wir also nicht mehr sicher mit Integerwerten rechnen. Da dies eine wichtige Obergrenze ist, können wir in JavaScript diesen Wert vom System erhalten, indem wir den Konstantwert „Number.MAX_SAFE_INTEGER“ auslesen. Hier versteckt sich auch der eigentliche Name des Zahlendatentyps von JavaScript, nämlich „Number“. Intern behandelt JavaScript also alle Zahlenwerte mit „Number“, was nichts anderes ist als eine „Gleitkommazahl doppelter Präzision“, sprich „double“. Man kann dies auch sehen, indem man eine Variable mit 0 initialisiert, mit -1 multipliziert um -0 zu erhalten, was laut Kapitel 6 in Java nur mit einem float (bzw. double) Datentyp möglich ist. Lediglich bei bitweisen Operationen wandelt JavaScript die Werte in 32 Bit Ganzzahlenwerte um.
Um nun den Wertebereich außerhalb der „max save integer“ Grenzen abdecken zu können, wurde der Datentyp „BIGINT“ eingeführt. Dieser hat einen (theoretisch) unendlichen Wertebereich, muss aber explizit als solches erzeugt werden. Dies geht in JavaScript entweder mit dem Zusatz „n“ nach der Zahl, oder man nutzt einen Konstruktor (womit wir also doch wieder bei einer Typisierung wären):
Listing 17: Erweiterung des sicheren ganzzahligen Bereiches in JavaScript
Kurze Erklärung der Codezeilen: Bei der Initialisierung mit einem BigInt Konstantwert wird das „n“ (ähnlich wie das „l“ in Java bei „long“) hinten angehängt. Wenn der Datentyp „BigInt“ ist, dürfen mathematische Operationen nur innerhalb dieses Datentyps durchgeführt werden, weshalb wir bei der Addition von 1 auch hier „1n“ sprich BigInt verwenden müssen. Bei der Nutzung des Konstruktors (für bigIntVar2) ist das „n“ nicht notwendig, da JavaScript hier weiß, dass höhere Werte als MAX_SAVE_INTEGER kommen können und der Interpreter den Parameter ähnlich wie einen String parst.
Wir sehen bei der Ausgabe, dass die Rechnung hier korrekt durchgeführt wird.
Jetzt, da wir wissen, dass in JavaScript (mit Ausnahme BigInt) jede Zahl als Gleitkommazahl verarbeitet wird, sollte die Ausgabe des folgenden Codes auch nicht verwundern:
Listing 18: Datentypen bei mathematischen Operatoren in JavaScript
Das Ergebnis ist natürlich 0.5 – insofern keine Besonderheit. Versuchen wir den gleichen Code nun mal in Java:
Listing 19: Datentypen bei mathematischen Operationen in Java
Führen wir diesen Code aus, erhalten wir 0. Wir werden uns um dieses Phänomen nochmal intensiver bei den Operatoren kümmern – so viel sei hier aber schon vorweggegriffen: In typisierten Programmiersprachen ist bei der Verrechnung von zwei gleichen Datentypen das Ergebnis immer gleich diesem Datentyp. Integer durch Integer ergibt also Integer – in unserem Fall also nicht 0.5 (was ja keine ganze Zahl ist), sondern eben nur der Ganzzahlenanteil 0. Dieses Verhalten gibt es bei nicht typisierten Programmiersprachen nicht – es kommt das „richtige“ Ergebnis 0.5 heraus. Dies gilt natürlich auch für PHP und Python.
Wie verhält sich nun PHP bei den Zahlenwerten – sprich wie legt PHP ganzzahlige Datentypen ab. Hier ist erstmal zu unterscheiden, ob wir die 32Bit oder 64Bit PHP-Installation nutzen, da hier die int Werte entweder als Int32, der klassischen Integer Interpretation, oder eben Int64, der Long Integer Interpretation abgelegt werden. Geht unser Wert über diesen Bereich hinaus, schwenkt PHP auf Gleitkommazahlen um:
Listing 20: Anpassung ganzer Zahlen bei Überlauf in PHP
Da auf meinem Referenzsystem eine 64Bit Installation ist, habe ich den Maximalwert auf 263-1 gelegt und um 1 erhöht.
Fehlt uns jetzt noch Python in unserer Sammlung. Hier geht man einen anderen Weg. Prüfen wir erstmal das Verhalten von Python:
Listing 21: Datentypverhalten von Python
Kurze Erklärung der Codezeilen: Der Code ist nicht wesentlich anders als in PHP oder JavaScript. Aufmerksam machen möchte ich hier kurz auf „str()“. In Python werden, wie auch in JavaScript, Strings mit dem + Operator verkettet, wobei JavaScript in der Lage ist, eine Zahl beim Verketten als String zu interpretieren. Dies macht mitunter bei Kombinationen wie „console.log("Summe: " + numVar1 + numVar2)“ Probleme. Die Frage ist schlichtweg, wird zuerst numVar1 mit numVar2 addiert und dann das Ergebnis mit dem String „Summe: “ verkettet, oder zuerst „Summe: “ mit numVar1 verkettet und dann numVar2 angefügt. JavaScript (und die anderen Sprachen, die „+“ als Verkettungsoperator haben) würden für den Fall, dass numVar1 den Wert „1“ und numVar2 den Wert „2“ enthält „Summe: 12“ ausgeben, sprich von links nach rechts verketten und nicht vorher summieren. In Python möchte man diesem Problem aus dem Weg gehen und verkettet ausschließlich Stings, weshalb man Zahlen vor der Verkettung mit „str()“ in Strings umwandelt.
Wenn wir den Code nun ausführen, erhalten wir:
Der Begriff „float“ ist hier nicht als „float“ Datentyp wie in den kompilierten Programmiersprachen zu verstehen, sondern als „Gleitkommazahl“. Python interpretiert also das Ergebnis einer Division zwingend als eine Gleitkommazahl - selbst wenn die Division durch 1 ist. Weiterhin fällt auf, dass Python einfache Datentypen auch als Klassen interpretiert. Dies schafft eine Flexibilität gegenüber den Maximalwerten – Python kennt (theoretisch) keine Obergrenzen von int. Dies kann man mit folgendem Code schön sehen:
Listing 22: Bitlängenanpassung bei Wertvergrößerung in Python
Kurze Erklärung der Codezeilen: Wir starten mit dem ersten Wert, der mit 64 Bit dargestellt werden muss. Bei der Ausgabe zeigen wir den Wert an. Da jeder Datentyp ein Objekt ist, können wir somit auch auf die Methoden der Objekte mit dem Punktoperator zugreifen. int kennt bspw. die Methode bit_length(), welche die Notwendige Bitlänge im Speicher anzeigt. Diese geben wir zusätzlich aus. Nach der Multiplikation mit 4 müsste nun entweder der Wert überlaufen, oder die Bitlänge sich verändern.
Führen wir den Code aus, erhalten wir:
Python vergrößert also automatisch den Speicherbereich für den größeren Wert. Der durchgängige objektorientierte Ansatz für Daten hat den Python Autoren nun auch die Möglichkeiten eröffnet, über die Grenzen der Rationalen Zahlen hinauszugehen. Python kennt bspw. „ab Werk“ komplexe Zahlen(3). Diese Flexibilität und Unbeschränktheit von Python erklärt auch ein Stück weit die große Akzeptanz von Python in Hochschulen.
(3) Eine kurze Einführung zu komplexen Zahlen folgt in einem späteren Kapitel
Top
Arrays
Jetzt, da wir einen groben Überblick über die Datentypen haben welche wir in kompilierten Sprachen als „primitive Datentypen“ verstehen würden, kümmern wir uns noch um ein wichtiges Konstrukt der Programmierung, den Arrays. Bei den kompilierten Programmiersprachen sind Arrays ja nicht dynamisch vergrößerbar.
In Skriptsprachen sieht das aufgrund ihrer Flexibilität durch die Interpretation anders aus. Die einzelnen Skriptsprachen bieten einen ganzen Strauß an Funktionalitäten an, mit denen man Arrays nach Belieben vergrößern, verkleinern oder sonst wie verarbeiten kann. Ich werde hier nur auf die wichtigsten Funktionalitäten eingehen. Wer einen tieferen Einblick benötigt, dem seien die API-Beschreibungen der einschlägigen Seiten (für JavaScript die Browserhersteller, w3schools.com oder selfhtml.org, für PHP php.net oder für Python docs.python.org) oder eine einfache Suche wie „JavaScript API Array“ empfohlen. Im Folgenden werde ich in JavaScript, Python und PHP wie bei den kompilierten Sprachen ein Array mit Initialwerten erstellen, einen Wert auslesen, danach mittig einen Wert hinzufügen, den vorletzten Wert löschen und einen Wert ersetzen. Danach werden alle Werte nochmal per Schleife ausgegeben. Beginnen wir mit JavaScript:
Listing 23: Arraymanipulation in JavaScript
Kurze Erklärung der Codezeilen: Das Array wird mit eckigen Klammern initialisiert. Alternativ würde man auch den Weg über einen Arraykonstruktor gehen können: „new Array("a", "b", "c", "e", "X", "F") oder für leere Arrays einfach new Array(6). Der Zugriff läuft wie in den kompilierten Sprachen über den Index in eckigen Klammern. Die wichtigste Funktion in JavaScript für die dynamische Arrayanpassung ist splice(). Hiermit kann man bspw. einen Wert hinzufügen: myArr.splice(3, 0, ["d"]). Dies bedeutet „Ändere das Array myArr ab der 3. Position, lösche 0 Elemente und füge dann das Teilarray ["d"] (also hier nur ein Wert) hinzu. Dadurch ist in dem Array "a", "b", "c", "d", "e", "X", "F" enthalten. Mit dem Aufruf „splice(5, 1)“ wird wieder ab der 5. Stelle verändert – diesmal jedoch wird 1 Element gelöscht und keines hinzugefügt. Die Ersetzung läuft über eine einfache Zuweisung an der entsprechenden Position. Am Schluss sehen wir eine in diesem Buch schon öfter gesehene Zählschleife über die Länge des Arrays myArr.length.
Die Ausgabe des Codes ist wie zu erwarten:
Leere zweidimensionale Arrays müssen in JavaScript etwas aufwändiger erzeugt werden. Da diese Konstrukte in JavaScript genauso wie in Java „Arrays von Arrays“ sind, ist folgende Vorgehensweise möglich:
Listing 24: Erzeugung 2D Array in JavaScript
Alternativ wurde mit ES6 auch folgende Option möglich:
Listing 25: Erzeugung 2D Array in JavaScript mit Array.from()
Kurze Erklärung der Codezeilen: Array.from() erzeugt aus dem ersten Argument ein Array – es sei denn, es handelt sich schon um ein Array, dann wird es 1:1 verwendet. Das zweite Argument ist eine Mapping Funktion. Hier wird für jeden Eintrag des Arrays mit => der rechts davon stehende Code aufgerufen. Es wird also pro Arrayeintrag ein neues Array erzeugt.
Sehen wir uns nun Python an. Hier müssen wir gleich wieder eine Besonderheit klarstellen. Python kennt unterscheidet zwischen Arrays und Listen. Das, was wir in PHP und JavaScript als Array verstehen, ist in Python eher eine Liste.
Listing 26: „Array“-Manipulation in Python
Arrays sind in Python im Wesentlichen typisiert – bzw. sie erlauben nur einen Datentyp, den man bei der Erzeugung auch als Parameter angeben muss. Sehen wir uns den Vergleich von List und Array hierzu an:
Listing 27: Liste vs. Array in Python
Kurze Erklärung der Codezeilen: Der import ist für das array Modul notwendig. Die Arrayerzeugung benötigt den Datentyp(4) (in unserem Fall Integer, weshalb das "i" angegeben wurde). Danach folgt eine Auflistung, deren Elemente vom angegebenen Datentyp sein müssen. Die Liste in myList wiederum benötigt keine Datentypspezifikation und kann Elemente verschiedener Datentypen aufnehmen. Würde die Auflistung [1, 2, "10", "20"] für die Arrayerzeugung eingetragen werden, so würden wir hiermit einen Fehler erzeugen.
(4) Weitere unterstützten Datentypen unter https://docs.python.org/3/library/array.html
Insgesamt ist das Array etwas effizienter als die List – sowohl in der Speicherbelegung als auch in Puncto Performance. An dieser Stelle möchte ich auch noch ein paar Zusätzliche Infos mitgeben. Python erlaubt auch das Erzeugen von leeren Listen (myList = []) wo wir mit myList.append("g") neue Elemente hinzufügen können, was performanter ist als mittels myList.insert(7, "g"). Zweidimmensionale Listen müssen auch hier etwas umständlicher erzeugt werden:
Listing 28: Erzeugung 2D Listen in Python
Weiterhin liefert Python noch die Bibliothek numpy, welche eine Vielzahl von mathematischen Tools für die Verarbeitung von Zahlen bietet und eine eigene Array Implementierung aufweist.
Und nun der Vollständigkeit halber noch die Funktionalität in PHP:
Listing 29: Arraymanipulation in PHP
Kurze Erklärung der Codezeilen: Im Wesentlichen finden wir Konstrukte, welche bereits in anderen Sprachen genutzt wurden, wie bspw. „splice“. Was bei PHP jedoch auffällt ist, dass die Grundbausteine nicht wirklich objektorientiert sind. Wir finden bspw. nicht wie in JavaScript oder Python die Möglichkeit, die Arraylänge direkt vom Array abzugreifen. Also anstatt $myArr.length() müssen wir eine externe Funktion nutzen und dieser das Array übergeben: count($myArr).
Eine Anmerkung zu PHP ist hier noch notwendig. Diese Sprache geht sehr flexibel mit Arrays um – wir können beispielsweise eine Variable als ein leeres Array deklarieren ($myArr = array();) und dann die einzelnen Arraypositionen belegen ($myArr[0] = "a"; $myArr[1] = "b";). Dies gilt auch für mehrdimensionale Arrays. Es ist also auch folgendes möglich: $myArr2 = array(); $myArr2[0][0] = "a";
Wir haben nun gesehen, dass in unseren Skriptsprachen Arrays grundsätzlich erstmal dynamisch sind. Bei den kompilierten Programmiersprachen haben wir aber noch einen zweiten Typus von dynamischen Arrays kennen gelernt, die assoziativen Arrays. Wie sieht es hier bei JavaScript und Co aus? Wie eigentlich zu erwarten war, haben die Macher der Skriptsprachen das Handling im Vergleich zu den kompilierten Programmiersprachen hier auch ziemlich vereinfacht. Sehen wir uns wieder zuerst JavaScript an:
Listing 30: Assoziatives Array in JavaScript
Kurze Erklärung der Codezeilen: Es wird also einfach ein leeres Array mit [] erzeugt, wobei Array() auch funktioniert. Danach wird einfach anstatt des Indexwertes der Key eingetragen und mit „=“ der Wert zugewiesen. Der Lesezugriff funktioniert entsprechend. Einträge würde man mit delete wieder löschen: delete cfgValues["DBType"];
Die Ausgabe ist somit:
JavaScript hat gegenüber dem klassischen assoziativen Array auch die Möglichkeit eine „Map“ zu nutzen. Es handelt sich hier um ein Objekt, welches mit „set()“, „get()“ und „delete()“ arbeitet. Details hierzu findet man wie immer in den einschlägigen Webseiten für JavaScript. Sehen wir uns nun die PHP-Lösung an. Bis auf die Tatsache, dass man ein assoziatives Array in PHP relativ einfach mit Initialwerten belegen kann, ist das Handling vergleichbar mit JavaScript:
Listing 31: Assoziatives Array in PHP
Das Löschen erfolgt in PHP mittels unset(). Wenn wir bspw. den Eitnrag für DBType löschen wollen, geht dies mit unset($cfgValues["DBType"]);. Schließlich fehlt uns noch Python, was bis auf ein paar Syntaxunterschiede wieder ähnlich zu PHP ist:
Listing 32: Assoziatives Array in Python
Python würde mit folgendem Kommando den DBType Eintrag löschen: del cfgValues['DBType']. Wie immer, gibt es also kleine Unterschiede, welche aber nicht darüber hinwegtäuschen, dass die eigentlich dahinterliegenden Konzepte sehr ähnlich sind. Der wesentliche Unterschied bei Skriptsprachen zu den kompilierten Sprachen ist wie immer die Flexibilität bei der Nutzung von Variablen. Dies kann man als Vereinfachung, aber auch als „unsauber“ interpretieren. Wenn wir uns beispielsweise PHP ansehen, so unterscheidet die Sprache zwangsweise aus Sicht der Deklaration nicht zwischen „normalen“ und assoziativen Ar-rays. Da es also bei der Erzeugung nicht festgelegt wird, können wir ein und dieselbe Variable sowohl als indiziertes, als auch assoziatives Array nutzen:
Listing 33: Assoziatives Array in PHP mit Indexzugriff
Kurze Erklärung der Codezeilen: Der neue Eintrag wird nun mit dem Key 5 vorgenommen und bei der Ausgabe mit dem Index 5 gelesen. print_r() formatiert ein Array (oder auch Objekt) in ein lesbares Format um.
Die Ausgabe lautet:
PHP behandelt also ein normales Array wie ein assoziatives, bei dem die Indexwerte als Keys zu verstehen sind. Dies ist bspw. beim Auslesen von Datenbankwerten interessant, da hier mitunter beide Ansätze untersützt werden. Tabellenspalten werden also über Key/Value Paare und/oder Indizes übergeben(5). Wenn wir die gleiche Übung in JavaScript machen, sieht die Sache wieder ganz anders aus:
Listing 34: Array mit Index und Key in JavaScript
Kurze Erklärung der Codezeilen: JavaScript gibt die Daten von Objekten und Arrays über
log()
strukturiert aus, sofern keine anderen Strings involviert sind. Die Struktur wird vor und nach der Ergänzung eines assoziativen Arrays um einen Indexzugriff ausgegeben.
Die Ausgabe ist wie folgt:
Durch das Setzen eines Wertes auf Indexposition 5 wird das assoziative Array um fünf leere Elemente am Anfang aufgefüllt, danach kommt der indizierte Wert und anschließend die Key/Value Paare. Hier sieht man, dass in JavaScript die Mischung aus assoziativen und indizierten Elementen eigentlich keine saubere Lösung ist und somit ohnehin vermieden werden sollte. Interessanterweise würde aber JavaScript einen Key, der einen reinen Zahlenwert darstellt ebenfalls als Index interpretieren:
Listing 35: Indiziertes Array in JavaScript trotz Verzicht auf Indexwerte
Die Ausgabe ist vergleichbar mit einem Array, welches mit Index und Keys erzeugt wurde:
Python setzt diese Funktionalität meines Erachtens für Skriptsprachen am nachvollziehbarsten um, da indizierte Arrays zwar wie assoziative Arrays behandelt werden, aber String Keys und Zahlenkeys (also Indizes) unterschieden werden:
Listing 36: Gemischtes Array in Python
Die Ausgabe beweist diese Aussage:
Top
Funktionsaufrufe
Sehen wir und nun mal die Skriptsprachen bezüglich Funktionsaufrufe an – vor allem was das „call by value“ und „call by reference“ Verhalten angeht. Da wir bei Skriptsprachen ja keinen direkten Zugriff auf die Speicheradressen haben und somit keine Pointerarithmetik auf dem Systemspeicher durchführen können liegt die Vermutung nahe, dass die Konzepte sich eher and Java und C# orientieren. Nutzen wir hier das gleiche Verfahren wie in Kapitel 8, indem wir den Wert eines primitiven Datentypen und eines Arrays in einem Unterprogramm ändern:
Listing 37: Übergabeverhalten in JavaScript
Der Code gibt, genau wie der Java Code aus Kapitel 8 die folgenden Werte aus:
Insofern war unsere Annahme richtig, dass die Umsetzungen identisch sind. Dieses Verhalten würde übrigens auch Python zeigen, was ich mir jetzt spare zu zeigen.
Was wir in dem JavaScript Code auch noch sehen ist, dass die Unterprogramme in JavaScript „function“ genannt werden, gefolgt von dem Funktionsnamen und der Parameterliste. Sehen wir uns nun ein Konzept an, welches in den objektorientierten Programmiersprachen gang und gebe ist, aber bei JavaScript aufgrund der fehlenden Typisierung(6) problematisch sein dürfte: das Überladen von Funktionen. Überladen bedeutet ja, dass man gleiche Funktionsnamen verwenden kann, die Unterscheidung zwischen den einzelnen gleichnamigen Funktionen über die Datentypen und Reihenfolge der Parameter erfolgt. Mangels Datentypen bei der Deklaration der Parameter in JavaScript gibt es hier also ein Problem. Versuchen wir es:
(6) Und der Stellung von Funktionen, was wir in Kürze näher betrachten werden
Listing 38: Versuch der Überladung von JavaScript Funktionen
Kurze Erklärung der Codezeilen: Wir haben zwei Funktionen, welche zwar gleich heißen, aber unterschiedliche Parameterzahl aufweisen. Zur Kontrolle, welche der beiden Funktionen aufgerufen wurde, wird „2 params“ bzw. „3 params“ ausgegeben. Der Rückgabewert beider ist die Summe aller Eingangsparameter, indem wir die Summe über return zurückgeben. Durch die beiden Aufrufe mit 1, 2 bzw. 1, 2 und 4 können wir somit prüfen, ob die Überladung funktioniert.
Die Ausgabe beweist nun, dass JavaScript keine Überladungen unterstützt:
Es wurde also in beiden Fällen die Funktion mit drei Parametern aufgerufen – auch bei dem Aufruf „doSum(1, 2)“. Die Funktion hat somit einen nicht initialisierten Parameter gefunden (nämlich „c“), weshalb die Summierung fehlschlug. Um solche Probleme jetzt in den Griff zu bekommen, haben die JavaScript Autoren noch die Möglichkeit des Defaultwertes eingeführt. Man kann die Parameterliste unseres zweiten „doSum“ nun wie folgt ergänzen: „doSum(a, b, c = 0)“. Dadurch erhält c einen Standardwert, wenn er nicht durch den Aufrufenden belegt wurde und wir können auf die erste „doSum“ Funktion verzichten.
Das Ganze scheint nun eine eingeschränkte Funktionalität gegenüber Java und Co. zu sein, da wir hier die Überladung relativ häufig nutzen. Der eigentliche Grund, warum das Überladen nicht funktioniert ist, dass in JavaScript Funktionen genauso behandelt werden wie Objekte – sprich man kann sie auf Variablen zuweisen:
Listing 39: Zuweisung von Funktionen in JavaScript
Kurze Erklärung der Codezeilen: Die Funktion add() wird erzeugt und gibt lediglich die Summe der beiden Parameter zurück. Nun weisen wir die Funktionsnamen add auf die Variable doAdd zu. Diese Zuweisung erfolgt ohne Parameter – wodurch die Identifikation einer Funktion einzig und alleine auf dem Funktionsnamen beruht. Nun zeigt die Variable doAdd auf die Funktion und wir können sie mit doAdd() aufrufen.
Die Ausgabe lautet somit 3. Man spricht bei JavaScript (und übrigens auch bei Python und PHP) davon, dass Funktionen „first class citicens“ sind. Hiermit möchte man ausdrücken, dass sie genau wie Objekte zugewiesen, übergeben oder zurückgegeben werden können. Im einem späteren Kapitel werden wir uns im Rahmen der funktionalen Programmierung nochmal darum kümmern. Untersuchen wir nun das Phänomen der Überladung in Python und lernen gleichzeitig den Python Syntax für die Funktionsdefinitionen:
Listing 40: Versuch der Überladung von Python Funktionen
Kurze Erklärung der Codezeilen: In Python werden Funktionen mit def festgelegt, gefolgt vom Funktionsnamen, Parameterliste und dann ein „:“. Was bei Python im Gegensatz zu JavaScript auffällt ist, dass wir zuerst die Funktionen schreiben müssen, bevor wir sie im Code nutzen. Wir können also die beiden Aufrufe der Funktionen erst nach der Definition durchführen.
Führen wir den Code aus, erhalten wir eine Fehlermeldung:
Python kann also auch nicht überladen, bietet aber genau wie JavaScript die Möglichkeit mit der „= 0“ Ergänzung einen Defaultwert zu setzen, wodurch der Code wieder korrekt läuft. Nun sieht man mitunter in Python die Funktionsdeklarationen mit typisierten Parametern. Man kann also festlegen, mit welchen Datentypen man zu tun hat. Sehen wir uns die Summierungsfunktion hierzu nochmal an:
Listing 41: Typisierte Parameter in Python
Kurze Erklärung der Codezeilen: Die Variablen a und b sollen jeweils vom Typ int sein und auch der Rückgabetyp wird mit Hilfe der -> Notation als int vorgegeben.
Soweit erstmal nichts Besonderes, außer dass man hier bei einer dynamisch typisierten Programmiersprache doch wieder Datentypen vorgibt. Die Frage ist nun, was bei einem Konflikt passiert. Provozieren wir also mal einen Fehler:
Listing 42: Ignorieren der Typvorgaben in Python
Kurze Erklärung der Codezeilen: Zuerst müssen wir über import sicherstellen, dass der Datentyp String überhaupt akzeptiert wird, da er nicht "primitiv" ist. Die Variablen a und b sollen nun vom Typ String sein. Den Rückgabetyp habe ich ebenfalls auf String gesetzt. Beim Aufruf übergebe ich aber wieder Zahlen. Den Rückgabewert addiere ich nun mit einer weiteren Zahl, was in Python bei Strings nicht möglich sein dürfte.
Führen wir den Code aus, sehen wir die Zahl 6. Python ignoriert also einfach die Typvorgaben. Also warum der ganze Aufwand, wenn er ohnehin ignoriert wird? Die Antwort liegt außerhalb von Python. Wenn wir mit Prüfungstools versuchen unseren Code auf potentielle Fehler zu überprüfen, dann spielen die Typisierungen oft eine große Rolle. Insofern hat man in Python zwar syntaktisch die Typisierung eingeführt, jedoch nur um einen „Typhinweis“ (engl. „type hint“) auf die erwarteten Datentypen zu hinterlegen. Die Interpretation des Codes erfolgt nach wie vor wie bei nicht (bzw. dynamisch) typisierten Aufrufen – Python legt erst zur Laufzeit die Datentypen fest. PHP bietet ebenfalls Typisierungen bei Funktionsaufrufen an, welche ebenfalls als „type hint“ gewertet werden.
Nun haben wir bei unseren bisherigen Untersuchungen des Aufrufverhaltens PHP außen vorgelassen. PHP müssen wir an dieser Stelle getrennt untersuchen, da PHP das Konzept „call by value“ und „call by reference“ explizit unterstützt und sich somit grundsätzlich anders verhält als JavaScript und Python. Testen wir wieder unser Übergabeverhalten mit unserem bekannten Ansatz:
Listing 43: Übergabeverhalten in PHP
Im Gegensatz zu JavaScript und Python, erhalten wir folgende Ausgabe:
PHP übergibt also auch ein Array als Value – es wird eine Kopie des gesamten Arrays angefertigt und übergeben! Wenn wir dies nicht wollen, müssen wir den „&“ Operator bei der Parameterdeklaration verwenden. Wir passen also die Funktionen aus Listing 43 an:
Listing 44: Übergabeverhalten in PHP – call by reference
Nun führen wir den Code wieder aus und erhalten:
Wir haben also in PHP, ähnlich wie in C, die Möglichkeit der expliziten Wahl, ob wir call by value oder by reference haben möchten. Der Unterschied liegt darin, dass dies nur in der Parameterdeklaration festgelegt wird und der Aufruf für beide Varianten gleichbleibt. Was nun noch fehlt ist die Frage der Überladung. PHP funktioniert hier genauso wie Python und JavaScript – wenn wir überladen, erzeugen wir eine Fehlermeldung. Nachdem aber auch Defaultwerte unterstützt werden, können wir diese Mechanik wieder für uns nutzen:
Listing 45: Defaultwerte für Funktionsparameter in PHP
Die Ausgabe auf der Konsole lautet:
Bevor wir uns auf die Strukturierung von Code in den Skriptsprachen stürzen, müssen wir noch eine sehr oft genutzte Syntaxvariante bei der Funktionendefinition in Skriptsprachen sprechen; und zwar die sogenannten anonymen Funktionen, welche wegen der möglichen Notation in PHP und JavaScript mit dem => Operator auch „Pfeilfunktionen“ genannt werden. Im Listing 25 haben wir eine solche Funktionalität im Rahmen von JavaScript bereits genutzt. Zeit, sich dieses Konstrukt mal genauer anzusehen. In Skriptsprachen können wir anstatt einer Funktion einen Namen zu geben, meist Funktionen einer Variablen zuordnen, wodurch der Variablenname scheinbar zum Funktionsnamen wird:
Listing 46: Anonyme Funktion in JavaScript
Bei Start des Codes sehen wir „Hallo Maik“ auf der Konsole. Was hier eigentlich passiert, ist dass wir eine anonyme Funktion – also eine Funktion ohne Namen – erstellen, welche wir einer Variablen zuweisen. Die Variable hält also eine Referenz auf eine Funktion. Ähnlich der Lambda Funktionen können wir nun diese Schreibweise abkürzen:
Listing 47: Pfeilfunktion in JavaScript
Da in unserem Beispiel die Funktion nur ein Statement enthält, könnten in JavaScript die geschweiften Klammern auch weggelassen werden. Gleiches gilt für die runden Klammern – wenn nur ein Parameter existiert, könnten diese auch entfernt werden. In solch einem Fall schreibt man die Funktion meist in einer Zeile:
Listing 48: Weglassen von Klammern in Pfeilfunktion bei JavaScript
Returnwerte werden wie in „normalen“ Funktionen mit return zurückgegeben:
Listing 49: Return bei Pfeilfunktionen in JavaScript
Wird jedoch nur eine Aktion durchgeführt, so kann auch hierauf verzichtet werden:
Listing 50: Verzicht auf return bei Pfeilfunktionen in JavaScript
Eine sehr vorteilhafte Nutzungsweise der Pfeilfunktionen in JavaScript können wir bei der Verarbeitung von Arrays zusammen mit der forEach() Funktion sehen:
Listing 51: Nutzung Pfeilfunktionen mit Arrays in JavaScript
Die Ausgabe lautet:
Hier sehen wir, dass der Code auf jeden Fall kompakter, wenngleich nicht immer übersichtlicher wird. In PHP finden wir ebenfalls die Möglichkeit von anonymen Funktionen:
Listing 52: Anonyme Funktion in PHP
Seit der Version 7.4 erlaubt PHP auch die Notation mittels des Pfeiloperators:
Listing 53: Pfeilfunktion bei PHP
Wichtig bei PHP im Zusammenhang mit dem Pfeiloperator ist, dass wir hier nach der Zuweisung fn() schreiben und nicht function(). Weiterhin darf nach dem => nur ein Ausdruck stehen – wir dürfen also nicht wie in JavaScript mit geschweiften Klammern komplexere Funktionaltäten erstellen. Genauso haben wir nicht die Option „return“ als Schlüsselwort einzutragen, es wird lediglich das Ergebnis des Ausdrucks zurückgegeben:
Listing 54: Pfeilfunktionen mit Rückgabewert in PHP
Python sieht hier ein eigenes Schlüsselwort für die Definition von anonymen Funktionen vor: lambda – ein Begriff, den wir aus Java ja bereits kennen:
Listing 55: Lambdaausdruck in Python
Auch hier gilt, dass wir als Lambdaausdruck nur einen Ausdruck verwenden dürfen – so wie es der Name ja schon nahelegt. Die Parameter werden zwischen lambda und dem Doppelpunkt kommasepariert hinterlegt. Rückgabewerte werden wie in PHP behandelt:
Listing 56: Lambdafunktion mit Rückgabewert in Python
Viele Skriptsprachen – und somit auch Python – ermöglicht uns mit map() und list() ebenfalls eine vereinfachte Verarbeitung von Arrays mit Lambdaausdrücken(7):
(7) Details hierzu sehen wir uns in einem späteren Kapitel zum Thema funktionale Programmierung nochmal an
Listing 57: Arrayverarbeitung mit Lambdaausdrücken in Python
Kurze Erklärung der Codezeilen: Diese Notation list(map()) verarbeitet das in map() hinterlegte Array names mit der vor dem Komma hinterlegten Funktion. Da wir diese als anonyme Funktion realisieren wollen, benötigen wir das lambda Schlüsselwort. Das nach dem Komma befindliche Array names wird somit Element für Element verarbeitet, indem jeder einzelne Wert aus dem Array in name übernommen und mit der Funktion verarbeitet wird.
Oft wird jedoch keine Ausgabe durchgeführt, sondern eine anderweitige Verarbeitung der Daten, welche wieder in einer anderen Liste abgelegt werden sollen:
Listing 58: Übertragung der verarbeiteten Arraydaten in eine Liste
Die Ausgabe lautet:
Es wurde also ein neues Array mit den veränderten Daten erstellt. Wir sind nun in der Lage, komplexere Funktionalitäten in einzelne Unterprogramme zu zerlegen, über Defaultwerte eine gewisse Flexibilität in der Handhabe von diesen Unterprogrammen zu erzeugen und sogar anonyme Funktionen definieren.
Top
Modularisierung
Der nächste logische Schritt ist nun die Frage, wie kann ich die einzelnen Funktionalitäten sinnvoll ordnen und wiederverwendbar machen, sprich „auslagern“. In den kompilierten Sprachen haben wir Schlüsselwörter wie „import“ oder „include“ um externe oder auch selbst erstellte Bibliotheken in unser Programm einzubinden. Eine solche Funktionalität wird auf jeden Fall auch für Skriptsprachen benötigt. Hier gibt es aber keinen Compiler (und Linker), der sich um das Suchen und Zusammenfügen der einzelnen Teilfunktionalitäten kümmert. Wir müssen also die ausgelagerten Funktionalitäten in eigene Skriptfiles schreiben und diese dann geeignet referenzieren. Zur Laufzeit werden diese Files dann geöffnet und verarbeitet. Hier lauern aber ein paar Probleme. Am besten sieht man das bei PHP. Wir lagern unsere Summierungsfunktion in ein eigenes File „sub1.php“ aus und legen dies in einem Unterverzeichnis „myfunctions“ an. Dort sehen wir auch gleich ein zweites File „sub2.php“ für PHP-Funktionalitäten vor:
Beispielhafte Ordnerstruktur
Abb.: 1: Beispielhafte Ordnerstruktur
Den Code unserer beiden Files main.php und sub1.php schreiben wir wie folgt:
Listing 59: Auslagerung von Code in eigene PHP Files
Kurze Erklärung der Codezeilen: Wir haben hier ein neues, wichtiges Programmiersprachenelement, den „Kommentar“ eingeführt. Wir werden später näher darauf eingehen. In der hier genutzten Form wird die gesamte Zeile rechts von dem „//“ Element nicht vom Interpreter ausgewertet, wir können also „Klartext“ schreiben und eben „kommentieren“. Danach folgt das include, welches den Pfad in Anführungsstrichen erwartet. Die Pfadtrenner sind „Forwardslashes“ – also “/“ wie in Linux oder iOS… übrigens funktioniert dieser Pfadtrenner so auch auf Windows Systemen, da sich der Interpreter darum kümmert, aus den Forwardslashes für Windows wieder Backslashes zu machen.
Führen wir den Code von main.php aus, sehen wir die 3 auf der Konsole – wir haben also in main.php eine Funktionalität aus einem anderen File (sub1.php) genutzt. So weit scheint das Ganze erstmal einfach zu sein. Komplizierter wird es, wenn es mehr Files werden. Erstellen wir sub2.php mit folgendem Code:
Listing 60: Untergeordnetes File referenziert weiteres File in PHP
Kurze Erklärung der Codezeilen: doSumAndPrint() nutzt nun die Funktionalität von doSum(), weshalb hier auch ein include notwendig ist. Da es im gleichen Verzeichnis zu finden ist, benötigen wir keine Pfadangabe. Die Funktion ruft nun doSum() auf, speichert den Wert, damit er ausgegeben und an den Aufrufer zurückgegeben werden kann.
Und nun passen wir main.php noch an, indem wir sub2.php inkludieren und die neue doSumAndPrint() Funktion nutzen:
Listing 61: Nutzung zweier untergeordneter Files in PHP
Kurze Erklärung der Codezeilen: doSumAndPrint() befindet sich in „sub2.php“, weshalb wir es auch einbinden müssen. Damit die beiden Ausgaben untereinander erscheinen, wurde noch ein Zeilenumbruch ausgegeben.
Wenn wir nun versuchen, das Programm zu starten, erhalten wir einen Fehlercode. Das Problem ist, dass PHP die Definition von doSum() zweimal liest und somit von einem Duplikat ausgeht. Wir können das Problem in dieser Situation dadurch lösen, dass wir in main.php auf den include von sub1.php verzichten, da wir diesen ja über den import in sub2.php bereits haben. Wir müssen also immer genau nachvollziehen, welche includes gerade wo stehen. Das ist nun sehr fehleranfällig. PHP liefert hier eine Abhilfe, indem wir in sub2.php include_once anstatt nur include verwenden. Das Problem hier ist, dass include_once inperformanter als include ist. Prinzipiell ist also include vorzuziehen, es sei denn, wir haben den Überblick über die includes verloren oder wir können aufgrund der Anzahl der involvierten Programmierer im Projekt die saubere include Struktur nicht garantieren. Neben include und include_once bietet PHP noch require und require_once an. Diese beiden Methoden funktionieren wie die include Methoden, allerdings haben sie ein anderes Fehlerverhalten, wenn das referenzierte File nicht gefunden wird. Während die „includes“ bei einer falschen Referenz einfach weiterarbeiten, brechen die „requires“ in solch einer Situation ab, was natürlich beim Entwickeln die bessere Alternative ist, da wir im Entwicklungsprozess die Fehler nicht „vertuschen“, sondern finden wollen.
Eine abschließende Verhaltensweise bei PHP includes/requires ist noch wichtig. Es ist immer zu empfehlen, dass in den inkludierten Files nur Code innerhalb Funktionen (und später Klassen) definiert wird, nie außerhalb. Ändern wir unser main.php und sub1.php wie folgt:
Listing 62: Variablen außerhalb Funktionen in PHP
Kurze Erklärung der Codezeilen: In main.php wird die Variable $myVar mit dem Wert „myValue“ erzeugt. In sub1.php haben wir eine Varaible mit gleichem Namen außerhalb der Funktion festgelegt und den Wert „yourValue“ eingetragen. In main wird nun $myVar ausgegeben.
Die Ausgabe zeigt, dass in sub1.php der Wert von $myVar überschrieben wurde, so dass in main.php „yourValue“ ausgebeben wird.
Die Variable $myVar ist also global zugreifbar. Da PHP keinen eigenen Code für die Variablendeklaration hat, fällt dieses Verhalten während der Codeerstellung nicht auf. Dies ist insofern problematisch, als dass man in größeren Projekten nicht immer weiß, wie die inkludierten Files intern aufgebaut sind, da man sie einfach als Modul nutzt. Hält man sich an die Regel, außerhalb des zu startenden Files die Variablen nur innerhalb von Funktionen zu nutzen, geht man diesem Problem aus dem Weg. Ändern wir bspw. sub1.php wie folgt ab, erhalten wir in der Ausgabe „myValue“:
Listing 63: Variablen innerhalb Funktionen in PHP
Innerhalb der Funktionen werden die Variablen also in einem eigenen Kontext gesehen, auch wenn sie gleich heißen. Möchte ich trotzdem innerhalb einer Funktion auf $myVar aus dem globalen Kontext zugreifen, muss vor der Nutzung von $myVar diese als global markiert werden: „global $myVar;“. Wenn nun der Code wieder ausgeführt wird, würde wieder „yourValue“ ausgegeben werden. Man erkennt hier also, dass der Vorteil der einfachen Nutzbarkeit von Skriptsprachen an einigen Stellen zu Sonderfällen führt, welche man mit zusätzlichen Schlüsselwörtern wieder in den Griff bekommen muss. Wir werden bei der Besprechung der kompilierten objektorientierten Sprachen sehen, dass man hier auf die Einfachheit der Anwendung zugunsten einer stringenten Anwendung von Konzepten und somit dieser „Extralocken“ verzichtet.
Werfen wir nun noch einen Blick auf Python. Hier wurde ein Modulkonzept eingeführt, um die imports besser strukturieren zu können. Wenn wir die gleiche Funktionalität wie unser PHP-Beispiel umsetzen möchten, muss unser Filesystem wie folgt aussehen:
Modulsystem in Python
Abb.: 2: Modulsystem in Python
Wir benötigen also in dem Unterordner ein neues File, namens „__init__.py“. Dieses kann erstmal leer sein. Python erkennt an diesem File, dass es sich um einen Modulunterordner handelt und platziert hier auch noch automatisch einen weiteren Ordner für zwischengelagerte Informationen. Wenn die Struktur so angelegt wurde, können wir die Python Files mit Inhalten füllen. Für den Code ist noch wichtig zu wissen, dass die Kommentardirektive für Python nicht der doppelte Slash wie in den meisten Programmiersprachen ist, sondern ein „#“ Zeichen:
Listing 64: Imports in Python
Kurze Erklärung der Codezeilen: Über „import“ können nun die einzelnen Python Files importiert werden – da es sie ja als Module und nicht als Files gesehen werden, ohne Anführungsstriche. Dadurch werden die Module automatisch geladen. Der Zugriff erfolgt dann über die Struktur „Modulname“.“Filename“.“Funktionsname“. Obwohl sich das File „sub2.py“ im gleichen Modul befindet wie „sub1.py“, muss der komplette Modulname hinterlegt werden. Man kann dies durch die Anpassung von sys.path etwas effizienter gestalten, indem bei Programmstart die entscheidenden Verzeichnisse eingetragen werden.
Wenn wir das Programm starten, erhalten wir wieder die korrekten Summationsergebnisse. Nun kann man in Python den Import so gestalten, dass der komplette Modulpfad beim Funktionsaufruf nicht eingetragen werden muss, was die Nutzung natürlich vereinfacht, sofern die Funktionsnamen über alle importierten Module eindeutig sind:
Listing 65: Import mit "from"
In JavaScript müssen wir dieses Thema in zwei Teile zerlegen. Wenn wir JavaScript im Kontext von HTML Seiten nutzen, dann werden meistens alle Files im „<script>“ Tag hinterlegt:
Listing 66: Einbindung mehrerer JavaScript Files in HTML Seiten
Mehr haben die „Macher“ von JavaScript den HTML Programmierern erstmal nicht zugemutet. JavaScript kennt mit ES6 offiziell solche Konzepte wie Module nicht. Dies ist insofern auch in gewissen Grenzen nachvollziehbar, da man in Webbrowsern im Regelfall einen begrenzten Kontext hat. Wir werden aber gleich eine Ausnahme sehen.
In der node.js Programmierung geht man über „exports“. Die Idee ist, dass die Submodule Funktionalitäten exportieren und so von außen zugänglich machen. Hierzu muss aber die referenzierende Stelle die entsprechenden Files über „require“ zugänglich machen:
Listing 67: Files in JavaScript unter node.js einbinden
Kurze Erklärung der Codezeilen: Die Einbindungen erfolgen über require, wobei hier einfache oder doppelte Anführungszeichen verwendet werden können. Ausgegangen wird vom aktuellen Verzeichnis des referenzierenden Files (wofür der „.“ im Pfad steht). Wie in PHP werden die „/“ Zeichen als Pfadtrenner genutzt. Das System kümmert sich darum, dass Doppelreferenzierungen nicht zu Problemen führen. Das „require“ Statement liefert eine Referenz auf das entsprechende File, welche in einer Variablen abgelegt wird. Beim Zugriff auf die Funktion muss dieser Variablenname, gefolgt von einem Punkt vor dem Funktionsaufruf stehen. Dadurch umgeht man Probleme mit doppelten Funktionsnamen aus verschiedenen Files. Die exportierenden Files müssen die Funktionen explizit „exportieren“, was mit „exports.“, gefolgt vom Funktionsnamen durchgeführt wird. Diesem Konstrukt wird dann die (anonyme) Funktion zugewiesen. Dadurch kann der Autor der referenzierten Datei festlegen, welche Funktionen von außen nutzbar sein sollen und welche nicht.
Ich möchte an dieser Stelle nicht zu tief in das Räderwerk der node.js Modulstruktur eintauchen. Trotzdem will ich noch eine Syntaxvariante zeigen, welche relativ häufig genutzt wird. In node.js wird jedes File als „module“ angesehen, welches wiederum eine „exports“ Eigenschaft besitzt. Über diese Eigenschaft erkennt der require Befehl, was er aus dem referenzierten File übernehmen kann oder soll. Wenn wir nun nur eine einzige Funktion (oder alternativ auch ein einziges Objekt) exportieren wollen, dann können wir dies auch über den Syntax module.exports durchführen und müssen dadurch dem exportierten Element keinen eigenen Namen geben. Sehen wir uns das in einem Listing nochmal an, indem wir sub2.js und main.js anpassen. Um die Flexibilität von „Funktionen als First Class Citizens“ nochmal darzulegen, habe ich hier zusätzlich nochmal sub1.js angepasst.
Listing 68: Files über module in JavaScript unter node.js einbinden
Kurze Erklärung der Codezeilen: Beginnen wir mit sub1.js. Hier habe ich die zu exportierende Funktion zuerst unter dem Namen mySumFunction erzeugt und erst anschließend unter dem Namen doSum exportiert.

In sub2.js habe ich nun über module.exports die Summenfunktion ohne Namen direkt exportiert. Dadurch kann ich nun in der main.js direkt mit s2(3, 4) den Aufruf durchführen – die Variable s2 wird also zur exportierten Funktion.
Eine Anmerkung sei bei der Nutzung von JavaScript auf dem Browser aber noch gemacht. Seit ES6 gibt es die Möglichkeit, Module zu verwenden. Dies ist Stand 2022 zwar „nur“ ein Proposal, wird aber von fast allen JavaScript Interpretern unterstützt. Die Idee ist, einzelne Funktionsblöcke als „Module“ zu kapseln, welche als Ganzes importiert werden können. Die Einbindung von Modulen erfolgt im Browser über die type Eigenschaft des script Tags:
Listing 69: Moduleinbindung in JavaScript für Browser
Hier noch eine kleine Anmerkung für den Beispielcode in https://github.com/maikaicher/book1. Die HTML-Seite kann nur über einen Webserver ausprobiert werden, da sonst die Sicherheitsfeatures des Browsers die Ausführung blockieren.
Für node.js nutzt man entweder anstatt „js“ Files nun „*.mjs“ oder man schaltet diese Funktionalität zentral frei. Hierfür wird ein eigenes File für die Zusammenstellung der Projekteigenschaften benötigt. Solche Files nennt man auch „Manifest“ Files. Für Node.js lautet es package.json und ist im Prinzip das Konfigurationsfile des Node Package Managers „npm“. Es würde an dieser Stelle zu weit führen, dieses Konzept näher zu beleuchten, insofern reduziere ich es an dieser Stelle auf das Konzept der Module. Hierfür erzeugen wir ein File namens package.json und tragen lediglich folgenden Inhalt ein:
Listing 70: package.json mit Freischaltung von Modulen
Eine weitere Möglichkeit dieses File zu erzeugen ist über den npm init Befehl. Diesen gibt man über die Konsole ein und folgt dem Konsolendialog. Dieses File muss anschließend noch um den Text aus Listing 70 erweitert werden. Wenn dies erfolgt ist, dann sieht der Export wie folgt aus:
Listing 71: Export als Modul
Und der dazu passende Import:
Listing 72: Import von Modulen
Wenn man den Alias (also „as myDoSum“) weglässt, dann ruft man die Funktion mit dem ursprünglichen Namen „doSum“ auf. Der Vorteil ist nun, dass man mehrere Elemente exportieren kann und nur einen import hierfür benötigt:
Listing 73: Export mehrerer Funktionen als Modul
Und der dazu passende Import:
Listing 74: Import von Modulen mit mehreren Funktionen
Den letzten Punkt, den wir bei Skriptsprachen besprechen müssen, ist das Speichermanagement. Wie man bereits vermuten kann, versteckten Skriptsprachen das Speichermanagement komplett vor dem Programmierer. Er hat, ähnlich wie in Java, keinen wirklichen Einfluss darauf, was im Heap und was im Stack landet und schon gar nicht, wie der Speicherplatz wieder freigegeben wird. Das hat im Wesentlichen zwei Gründe. Das Ziel von Skriptsprachen war ja, die Arbeit des Programmierers möglichst zu vereinfachen. Es wäre somit kontraproduktiv, dem Programmierer das Speichermanagement aufzubürden. Der zweite, wahrscheinlich wichtigere Grund ist, dass zwischen dem von uns geschriebenen Code und dem System – bestehend aus Prozessor, Speicher und Betriebssystem – die Laufzeitumgebung mit dem Interpreter sitzt und dessen Speicherverwaltung erstmal intern gelöst werden muss. Die meisten Laufzeitumgebungen sind selbst in C++ geschrieben, wo man sich ja selbst um das Speichermanagement kümmern muss. In diesem Zusammenhang konnte man sich auch gleich um das Managen des Speichers für den interpretierten Code kümmern. Die Notwendigkeit der Laufzeitumgebung haben wir bei Java und C# auch, weshalb sich Java Entwickler ähnlich „wenig“ Gedanken über das Speichermanagement machen müssen wie Skriptsprachenprogrammierer.
An dieser Stelle möchte ich nochmal kurz auf die „Grabenkämpfe“ von ambitionierten Skriptsprachenentwicklern versus ambitionierten C++ Entwicklern zu sprechen kommen. Ist der Ansatz von Skriptsprachen, dass der Rechner sich um Datentypen und der Garbage collection kümmert, nun besser oder schlechter als sich um alles selbst zu kümmern? Eine sinnvolle Antwort auf diese Frage gibt es nicht, da die Frage als solches schon falsch ist. Es wäre genauso töricht zu fragen, ob in einem Auto ABS jetzt gut ist oder nicht. Für den normalen Privatfahrer ist es auf jeden Fall sinnvoll, sich ein Fahrzeug mit ABS zu kaufen. Allerdings wird es keinen professionellen Rallyefahrer geben, der sich ernsthaft ABS in sein Rennauto bauen lässt. Es kommt wie immer auf den Anwendungsfall an.
Top
CC Lizenz (BY NC SA)
CC Lizenz (BY NC SA)