Höllische Programmiersprachen Seminar im Wintersemester 2014

Werbung
Höllische Programmiersprachen
Seminar im Wintersemester 2014/2015
Typsystem vs. Laufzeitimplementation
Andrea Müller
Technische Universität München
18.12.2014
Zusammenfassung
1
Diese Ausarbeitung untersucht die Auswirkungen eines Typsystems
einer Programmiersprache auf das Laufzeitsystem und die Folgen für das
Programm. Hierbei wird sowohl auf die verschiedenen Typisierungen eingegangen, als auch ihre Vor- und Nachteile abgewägt. Unterstützt und
verdeutlicht wird die Ausführung des Themas durch praktische Beispiele
und Programmcodeauszügen.
2
Inhaltsverzeichnis
1 Einleitung: Typsysteme
1.1 Was sind Typsysteme? . . . . . . . . . . . . .
1.2 Einordnung von Typsystemen . . . . . . . . .
1.2.1 Starke und Schwache Typisierung . . .
1.2.2 Statische und Dynamische Typisierung
1.2.3 Explizite und Implizite Typisierung .
2 Hauptteil: Verbindung von Typ- und
2.1 Typisierte Sprachen in der Praxis . .
2.1.1 Java . . . . . . . . . . . . . .
2.1.2 C . . . . . . . . . . . . . . . .
2.1.3 Weitere typisierte Sprachen .
2.2 Vor- und Nachteile . . . . . . . . . .
3 Schluss: Fazit
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
4
4
5
5
Laufzeitsystem
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
8
10
11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
3
1
Einleitung: Typsysteme
In der Informatik sind Programmiersprachen das zentrale Werkzeug. Durch sie
kann der Programmierer mit dem Computer kommunizieren, Programme erstellen und Algorithmen ausdrücken. Eine Hochsprache soll dem Programmierer
diese Arbeit erleichtern. Häufig wird von einem Typsystem bestimmt, welche
verwendbaren Programmiertechniken existieren. In manchen Fällen kann das
Laufzeitsystem dieses Niveau allerdings nicht halten. Dies wirkt sich dann an
einigen Stellen negativ auf die Möglichkeiten der Hochsprache aus und schränkt
die Qualität des Programms ein. Im Folgenden werden verschiedene Typsysteme
vorgestellt und besonders auf ihre Eigenschaften und deren Folgen eingegangen.
1.1
Was sind Typsysteme?
Viele Programmiersprachen stellen ein Typsystem zur Verfügung, welches Ausdrücke zur Übersetzungszeit anhand ihrer Struktur klassifiziert, um semantisches Fehlverhalten dieser Ausdrücke zu vermeiden. Dafür werden die Typregeln einer Programmiersprache spezifiziert, indem das Typsystem eine formale
Grammatik für die Syntax dieser Sprache aufstellt. Dabei sind die Regeln unabhängig von den Algorithmen zur Typüberprüfung im Compiler, da sie zur
Definition einer Sprache gehören. Verschiedene Compiler können dadurch für
das selbe Typsystem mehrere typüberprüfende Algorithmen benutzen.
Typisierte, im Gegensatz zu nicht typisierten Sprachen, führen also Programme
nicht nur strikt aus, sondern prüfen ihre Beständigkeit [3].
1.2
Einordnung von Typsystemen
Programmiersprachen können in mehrere, sich überschneidende Kategorien aufgeteilt werden. Im Folgenden werde ich diese kurz aufzeigen.
1.2.1
Starke und Schwache Typisierung
Als stark typisiert lassen sich alle Sprachen bezeichnen, die sicherstellen, dass
der Zugriff auf alle Daten und Objekte typkonform bleibt[5]. Will man den Datentyp eines Objekts also manuell ändern, muss dies über Umwege geschehen.
Dabei können die Daten, die im Speicher abgelegt werden, ausschließlich durch
ihren ursprünglichen Datentyp interpretiert werden. Hierbei stellen stark typisierte Sprachen oftmals kein, oder nur wenige Mechanismen zur Datentypänderung bereit.Als Beispiel können die Sprachen Haskell, Java und Lisp genannt
werden[5].
Im Gegensatz dazu weist eine schwach typisierte Sprache Lücken auf. Sprachen,
die in diese Kategorie fallen, sind zum Beispiel C, C++ und Perl. Bei ihnen
kann der Datentyp jederzeit uminterpretiert werden. Dies geschieht meistens
mit type casts, der das Speicherabbild der Variable nicht verändert sondern
schlicht uminterpretiert [5]. Die Verantwortung über die Sinnhaftigkeit des Programms obliegt dem Programmierer. Es gibt allerdings auch Ausnahmen, wie
4
beispielsweise eine Integer-Float-Konversion, bei der der C-Compiler eine Transformation des entsprechenden Objekts durchführt bevor die Zuweisung zu einer
Variable geschieht. Diese Einteilung enthält allerdings Überschneidungen, da es
auch möglich ist von Programmen mit starken Sprachen aus Funktionen aus
schwachen Sprachen auszuführen. Als Beispiel sei hier das Java Native Interface
genannt, welches C-Funktionen bereitstellt.
1.2.2
Statische und Dynamische Typisierung
Eine Sprache heißt statisch typisiert, wenn die Variablen mit ihren Datentyp im
Quelltext deklariert werden. Dabei kann dieser Variable ausschließlich ein Objekt zugewiesen werden, sofern dieses kompatibel mit dem statisch festgelegten
Datentyp ist.
Dies passiert in Sprachen wie Scala, Haskell, Java, C, C# und C++, wohingegen Perl, PHP und Phyton Beispiele für dynamische Typisierung sind.[7] [6] Bei
ihnen sind die Variablen nicht an Objekte gebunden. Deshalb kann zu verschiedenen Zeitpunkten ein und dieselbe Variable unterschiedliche Objekte referenzieren. Bei dieser Aufteilung darf beispielsweise statische Typisierung nicht mit
statischer Typprüfung verwechselt werden. Letzteres bezieht sich darauf, wann
die Typüberprüfung stattfindet. Bei der statsichen Prüfung findet diese während
der Übersetzungszeit statt, bei der Dynamischen hingegen erst zur Laufzeit.
1.2.3
Explizite und Implizite Typisierung
Explizit typisiert ist eine Bezeichnung für Programmiersprachen, bei denen der
Programmierer Elementen Typangaben zuordnet. Als Beispiel ist Java zu nennen, da der Programmierer hier meistens gezwungen ist, den Datentyp explizit
anzugeben. In Sprachen wie beispielsweise Haskell ist es jedoch möglich Typen
per Typinferenz zu bestimmen. Dies wird meist durch Prüfen der Typsignatur,
die ein Typsystem zur Verfügung stellt, ermöglicht.
2
2.1
Hauptteil: Verbindung von Typ- und Laufzeitsystem
Typisierte Sprachen in der Praxis
Treten in einem Programm spontan Fehlersituationen auf, so regeln in den
meisten Programmiersprachen Ausnahmebehandlungen den Umgang mit diesen Fehlern. Dabei gibt es Fehler wie ungültige Eingaben oder nicht gefundene
Dateien, die geplante Ausnahmen genannt werden, und es gibt beispielsweise
Speichermangel oder die Division durch Null, die als ungeplanten Ausnahmen
bezeichnet werden. Verursacht ein C-Programm beispielsweise einen Divisondurch-Null-Fehler, dann wird dieser durch eine Interrupt Routine während der
Laufzeit behandelt. Dabei ist eine sofortige Beendigung des Prozesses die Folge.
In Java wird die Behandlung einiger Fehler dem Programmierer überlassen. Eine Division durch Null würde hier eine Exception werfen, welche abgefangen
5
werden kann. Dies bietet dem Programmierer die Chance, auf dieses Szenario
zu reagieren.[5]
Warum die Überprüfung erst zur Laufzeit stattfinden lässt sich leicht beantworten. Durch die späte Prüfung wird ein mächtigeres Verhalten zugelassen,
wie z.B die richtige Verarbeitung von Erweiterungen zur Laufzeit und Typen,
die normalerweise inkompatibel zueinander sind. Um einige Beispiele solcher
Fehlersituationen zu zeigen, sollte folgende Tabelle einen Überblick über die
Kategorisierung spezieller Programmiersprachen geben: [5],[8]
Tabelle 1: Einordnung der Sprachen
Statische Typisierung
Dynamische Typisierung
• C
• PHP
• C ++
• Perl
Schwach
• Delphi
Stark
2.1.1
• Java
• Python
• C#
• Smalltalk
• Haskell
• Lisp
• ML
• Scheme
Java
Java ist eine statische, explizite und starke Sprache. Als starke Sprache schützt
Java seine Abstraktion, indem bei folgendem Code in Abhängigkeit von a eine
Exception geworfen wird:
Integer doSth (Object a, int b) {
int add = (int)a + b;
}
Der Methode doSth werden zwei Parameter übergeben, eine Variable a
vom Typ Object und eine Variable b vom Typ int. Der Datentyp von a ist kein
Basistyp und es wird eine ClassCastException, oder sogar eine NullPointerException gemeldet, falls a ganz undefiniert ist. Beide Exceptions werden während
der Laufzeit entdeckt, da das Laufzeitsystem erst während der Laufzeit testen
6
kann, welchen Typ die Variable a besitzt, die zur Kompilierzeit vom Typ Object
ist. Folgendes Beispiel führt ebenfalls zu einer ClassCastException:
public class Liste{
public static void main(String argv[]) {
List myList = new ArrayList();
myList.add(new String(“Hel”));
myList.add(new String(“lo”));
myList.add(new Integer(42));
Iterator iterates = myList.iterator();
while(iterates.hasNext()) {
String point = (String) iterates.next();
System.out.println(point);
}
}
}
Das Java Programm befüllt eine Liste mit Strings und einem Integer.
Anschließend wird auf der Konsole ausgegeben. Dabei kann List als Klasse
jedes Objekt aufnehmen, dass von der Klasse Object stammt. Diese bildet
in Java das obere Ende der Klassenhierarchie. Dadurch wird List zu einem
generischen Container zur Speicherung von Objekten. Diese Flexibilität wird
durch das Typsystem zur Verfügung gestellt, jedoch wird zur Kompilierzeit
nicht erkannt, dass der Typ der gespeicherten Listenelemente zum Abbruch
des Programms führen kann. Ausgelöst wird dies durch den letzten Befehl,
in dem ein Objekt vom Typ Integer angehängt wird. Allerdings erwartet die
Schleife, welche die Liste ausgeben soll, ausschließlich gespeicherte Objekte
vom Typ String. Zur Übersetzungszeit wird der Code fehlerfrei übersetzt, doch
zur Laufzeit wird ein Typkonflikt erkannt. Das Programm bricht in der dritten
Iteration der Schleife ab und es wird eine ClassCastException ausgegeben.
Auch in diesem Fall wird ein Fehler während der Laufzeit entdeckt:
int[] numbers = new int[10];
for(int i = 0; i ≤ numbers.length; i++) {
numbers[i] = 0;
}
Hier wird ein Array erstellt, das 10 Elemente vom Typ int aufnehmen
soll. Die Schleife, die durch diese Arrayelemente iteriert, löst letztendlich eine
ArrayIndexOutOfBounds Exception aus, da sie im letzten Durchgang auf ein
Element hinter dem letzten Arrayelement zugreifen will. Ein weiteres wichtiges
Konzept ist die Typzerstörung oder type erasure. In Java werden generische
Anwendungen vom Compiler übersetzt. Ein Code der ehemals folgendermaßen
aussah
public class Pocket<T>{
7
private T value;
public void set( T value ){
this.value = value; }
public T get() { return value;}
}
wird nach dem Kompilieren so aussehen und an das Laufzeitsystem übergeben:
public class Pocket {
private Object value;
public void set( Object value ){
this.value = value; }
public Object get() { return value; }
}
Da die Laufzeitumgebung von Java keine Generics im Typsystem hat, löscht
der Compiler alle Typinformationen. Diese bestehen somit zur Laufzeit nicht
mehr. Die Folge davon ist allerdings, dass Folgendes nicht mehr möglich ist:
class Pocket <T >{
T newPocketContent() {
return new T(); }
}
Nach dem Kompilieren würde “new T()” nicht mehr funktionieren, da
dieses durch “new Object()” ersetzt wurde und nun ein neues Objekt erstellt
wird, obwohl man ein Objekt vom Typ T erwartet.
2.1.2
C
In der statisch und schwach typisierten Sprache C treten häufig Fehler auf,
die sich auf eine falsche Verwendung des Typsystems zurückführen lassen.
Nachfolgend ist ein Beispiel dargestellt:
static int mL{
// Angabe in Millilitern
int mass = 50;
return mass;
}
int main(int argc, char *argv[]) {
// Angabe in Litern
int water;
water = mL();
8
printf(“Water mass = %d Liter”, water);
return 0;
}
Hier wird die Methode mL aufgerufen, die eine Variable vom Typ int zurückgibt.
Diese Funktion wird in der main Methode aufgerufen und der Rückgabewert
wird in der Variable water gespeichert. Anschließend wird der Wert von water
auf der Konsole ausgegeben. Betrachtet man die Kommentare im Code, so
stößt man auf einen Typkonflikt. Die main-Methode geht von einem Wert in
der Einheit Liter aus, wobei die Methode mL von der Einheit Milliliter ausgeht.
Durch die zeitgleiche Nutzung des Datentyps int wird der ’Fehler’ während der
Kompilierzeit offensichtlich nicht erkannt. C bietet jedoch die Möglichkeit an,
den typedef Konstruktor
typedef int ml;
typedef int liter;
zu verwenden und somit die Bezeichnung der Datentypen benutzerdefiniert
erzeugen zu lassen. Diese lassen sich genauso wie der Datentyp int verwenden.
Modifiziert man die Methoden damit, lässt sich die Typinkonsistenz leichter
erkennen und die Qualität durch höhere Transparenz steigern:
typedef int ml;
typedef int liter;
static ml mL{
// Angabe in Millilitern
ml mass = 50;
return mass;
}
int main(int argc, char *argv[]) {
// Angabe in Litern
liter water;
water = mL();
printf(“Water mass = %d Liter”, water);
return 0;
}
Trotzdem wird der Typkonflikt noch nicht vom Kompilierer erkannt und
das Programm wird weiterhin fehlerfrei übersetzt. Der Grund dafür liegt
am typedef -Mechanischmus. Dieser definiert lediglich ein Synonym fà 14 r den
bereits existierenden Datentyp. Eine endgültige mögliche Lösung des Problems
ließe sich durch eine Kapselung finden, die bewirkt, dass die beiden Datentypen
9
während der Kompilierzeit unterschiedlich behandelt werden. Eine mögliche
Umsetzung sieht folgendermaßen aus:
typedef struct { int value; } ml;
typedef struct { int value; } liter;
static ml mL{
// Angabe in Millilitern
ml mass = {50};
return mass;
}
int main(int argc, char *argv[]) {
// Angabe in Litern
liter water;
water = mL();
printf(“Water mass = %d Liter”, water.value);
return 0;
}
2.1.3
Weitere typisierte Sprachen
Die bereits vorgestellten Sprachen Java und C sind beide statisch typisierte
Sprachen. Ihre Compiler sind oft größer und umfangreicher als bei dynamisch
typisierten Sprachen wie Perl und PHP. Bei ihnen findet die Typüberprüfung
erst zur Laufzeit statt. Im Perl Code
$x = 3.5;
$x = $x + “8-6”;
$x = $x + “test”;
ist x erst die Zeichenkette “3.5”, dann “11.5”, da nur die 8 addiert wurde und letztendlich bleibt x unverändert, da “test” nicht mit einer Zahl
beginnt. An dieser Stelle geht deutlich hervor, dass das Typsystem von Perl
solche Fälle nicht abdeckt. Genau wie Perl ist auch PHP eine dynamische,
implizite und schwach typisierte Sprache. Sie verhält sich ähnlich wie Perl, was
man an folgendem Beispiel erkennt:
$x = 5;
$x = 13 + “59”;
$x = 10 + “test”;
Hier wird x erst der Wert 5 zugewiesen. Dann wird die Zeichenkette in
10
eine Zahl umgewandelt und mit 13 addiert. $x hat nun den Wert 72. In der
dritten Zeile wird die Zeichenkette “test” zu 0 gewandelt und $x nimmt den
Wert 10 an. Die starke Sprache Python geht mit den selben Aufrufen anders um:
x
x
x
x
=
=
=
=
5;
13 + “59”;
13 + int(“59”);
10 + int(“test”);
In der ersten Zeile wird x wieder der Wert 1 zugewiesen. Bei der Addition der Zahl mit der Zeichenkette würde jedoch der Laufzeitfehler TypeError
erscheinen. Die Lösung des Problems ist das Umwandeln der Zeichenkette in
einen Datentyp int. Dies ist in Python möglich und x nimmt den Wert 72
an. Bei dem Versuch eine Zeichenkette in einen int umzuwandeln, die nicht
umwandelbar ist, tritt ein weiterer Laufzeitfehler, ValueError auf.
Um noch ein Beispiel zu nennen, das eindeutig schwache von starken
Sprachen unterscheidet, betrachte man diese Programmzeile:
x = “5” + 6
Die Wertzuweisung von x hängt hier von der Sprache ab. Die schwach
typisierte Sprache JavaScript wandelt den Wert 6 in einen String um und
konkateniert beide Argumente. Das Ergebnis wäre der String “56”
Die ebenfalls schwachen Sprachen PHP und Perl wandeln “5” in eine Zahl um
und addieren beide Argument. Die Variable x hätte nun den Wert 11.
In C würde der Wert “5” in einen Pointer gewandelt, der auf die Speicheradresse
zeigt, in der der String gespeichert ist. Daraufhin würde die Adresse dann
den Wert 6 addieren und es entstünde eine andere Speicheradresse. Obwohl
C ebenfalls schwach ist, wäre eine Sicherheitsverletzung möglich. Eine stark
Typisierte Sprache würde die Typinkonsistenz schon während dem Kompilieren
erkennen oder aber eine Fehlermeldung anzeigen. Die Sprache BASIC würde
das Programm gar nicht erst laufen lassen, da der Fehler schon vom Compiler
erkannt und zurückgewiesen wird.
Die starke Sprache Ruby würde während der Ausführung anzeigen, dass die
Additionsoperation ill-typed ist.
2.2
Vor- und Nachteile
Aufgrund der aufgezeigten Ausnahmebehandlungen stellt sich die Frage was
für die jeweiligen Typisierungen spricht, da diese die Qualität des Programms
beeinflussen. Die folgende Tabelle soll die Vorteile übersichtlich darstellen.
Wie in Tabelle 2 aufgezeigt, profitieren statisch typisierte Sprachen von ihren
zwingend vorhandenen Deklarationen, da der Compiler durch diese zu jeder
Zeit den Variablentyp kennt. Dadurch werden Typinkonsistenzen durch simple
Vergleiche schon während der Übersetzungszeit entdeckt. Darüber hinaus kann
11
Tabelle 2: Vor -und Nachteile der Typisierungen
Vorteile
Nachteile
Statisch:
• Geringe Flexibilität
• Compiler erkennt Typinkonsistenz sofort
• Deklaration der Datentypen
kostet Zeit
• Funktionen wie automatisch
erzeugte Komfortfunktionen
möglich
Dynamisch:
• Inkonsistenzen erst zur Laufzeit erkannt
• Hohe Flexibilität
• Steigerung der Produktivität
• Geringe Typinformation
• Geringere Zeitkosten
Schwach
• Hohe Fehleranfälligkeit
• Flexibilität
• Optimierungspotenzial
Stark:
• Erschwerte Datentypänderung
• geringe Fehleranfälligkeit
• Exceptions verhindern Systemabsturz (Java)
es von Vorteil sein, den Datentyp genau zu kennen, wenn man den erzeugten
Maschinencode zu optimieren versucht.[5]
Zusätzlich können Entwicklungsumgebungen die Typinformationen ausnutzen,
z.B für die Einfärbung des Quelltext während der Bearbeitung. Außerdem
gehören zum heutigen Stand der Technik unter anderem auch Kontextmenüs,
die alle Methoden auflisten, die für die aktuell ausgewählten Variablen zur
Verfügung stehen. Dies kann mit nicht statisch festgelegten Datentypen nur
eingeschränkt realisiert werden. Die Nachteile bei der statischen Typisierung
sind gleichzeitig die Vorteile der dynamischen. So ziehen diese ihren Vorteil aus
ihrer deutlich höheren Flexibilität.
Die gesteigerte Produktivität, wie sie in Tabelle 2 aufgezählt wird, reicht daher,
dass die Deklaration einer Variable nicht vor ihrer Verwendung stattfinden
12
muss. Der Begriff rapid prototyping wird oft mit dynamisch typisierten Sprachen verbunden, da diese sich durch die schnelle Erstellung von Programmen
gut für die Erzeugung von Prototypen eignen. Auf der negativen Seite lässt sich
aufführen, dass die geringe Typinformation auch das Optimierungspotenzial
des Compilers einschränkt.[5]
Schwach typisierte Sprachen bieten eine große Interpretationsfähigkeit für
die im Speicher abgelegten Daten und ermöglichen dadurch eine hohe Flexibilität und ebenfalls Optimierungspotenzial. Da der Programmierer allerdings
die volle Verantwortung für die Typkonsistenz trägt, entsteht eine erhöhte
Fehleranfälligkeit.[5]
Im Gegensatz dazu stellen stark typisierte Sprachen eingeschränkte oder sogar
keine Mechanismen zur Verfügung, um den Variablentyp zu ändern. Dies
zeichnet sich vorallem in der Sprache Haskell aus, bei der auf type-casts zur
Veränderung der Datentypen verzichtet wird. Java stellt trotz ihrer starken
Typisierung dabei eine Ausnahme dar. Laut Sprachdefinition ist es in Java
erlaubt, einen Variablentyp zu ändern, was sie aus den stark typisierten
Sprachen ausschließen sollte. Jedoch werden Inkonsistenzen hier durch die
Laufzeitumgebung erkannt und Verletzungen durch Exceptions behandelt.
Dadurch wird das Programm im Fehlerfall unterbrochen und es kommt nicht
zum Systemabsturz.[5]
3
Schluss: Fazit
Typisierungen einer Sprache helfen vor allem dem Programmierer, sei es durch
Einfachheit, Lesbarkeit und Fehlererkennung. Typsysteme bringen Ordnung in
das Chaos, das ensteht, wenn eine Vielzahl an Datenworte während der Laufzeit
eines Programms verarbeitet werden. Heutzutage setzen fast alle Programmiersprachen das Prinzip der Typisierung ein, denn sie ist eines der mächtigsten
Hilfsmittel zur Qualitätssicherung und entlastet den Programmierer, indem es
ihm einige Verantwortung für ein korrekt funktionierendes Programm abnimmt.
Wie sich in Kapitel 2.1 gezeigt hat, wirken sich Typsysteme oft auf die Laufzeitumgebung aus, denn selbst in stark typisierten Sprachen haben die benutzten
Typsysteme Sicherheitslücken. Wie groß diese Lücken sein können wurde im
Hauptteil aufgezeigt. Die Nachteile, die in Kapitel 2.2 aufgezeigt wurden, die
jede Typisierung mit sich bringt, unterstützen die These. Auch Laufzeitsysteme haben ihr Grenzen. Obwohl die versuchen Fehler zu beheben, sei es durch
Exception-Handler oder ähnliches, ist dies nicht in jedem Fall möglich und es
kommt zum Systemabsturz. Letztendlich ist festzuhalten, dass es negative Auswirkungen auf das Laufzeitsystem gibt, aber auch positive. Wie Programmierer
zukünftig mit diesen Auswirkungen umgehen, wird sich erst noch zeigen.
13
Literatur
[1] Gilbert Brands. Das C++ Kompendium. Springer-Verlag, 2005.
[2] Danny Goodman. JavaScript Bible. Hungry Minds, 2001.
[3] Robert Harper. Practical Foundations for Programming Languages. Cambridge University Press, 2012.
[4] Tobias Hauser. JavaScript Kompendium. Markt+Technik Verlag, 2003.
[5] Dirk W. Hoffmann. Software-Qualität . Springer Verlag, 2008.
[6] Liberty J. Programmieren mit C#. O’Reilly and Associates, 2005.
[7] P. Pepper. Funktionales Programmieren in OPAL, ML, HASKELL, GOFER. Springer Verlag, 2003.
[8] Benjamin J. Pierce. Types and Programming Languages. The MIT Press,
2002.
[9] Zhong Shao. Programming Languages and Systems. Springer-Verlag, 2014.
[10] H. Trauboth. Software-Qualitätssicherung. Oldenburg-Verlag, 1996.
[11] Peter Wegner. Concepts and paradigms of objectoriented programming.
1990.
14
Herunterladen