Programmieren für Fortgeschrittene 1. Einführung Werner Struckmann Technische Universität Braunschweig Institut für Programmierung und Reaktive Systeme Wintersemester 2013/2014 1. Einführung 1.1 Sprachen und Paradigmen 1.2 Definition von Programmiersprachen 1.3 Implementierung von Programmiersprachen 1.1 Sprachen und Paradigmen 2/38 Sprachen • Sprache ist ein sich stets weiterentwickelndes, komplexes System von Lauten und Zeichen zum Zwecke der Kommunikation. Jedem Zeichen des Systems wird eine feststehende Bedeutung zugeordnet. • Sprache wirkt im Prozess der Kommunikation als Medium zwischen dem Sender (Sprecher, Schreiber) und Empfänger (Hörer, Leser). • Es werden natürliche und künstliche Sprachen unterschieden. aus Basiswissen Deutsch, Dudenverlag 1.1 Sprachen und Paradigmen 3/38 Natürliche, künstliche und formale Sprachen • Natürliche Sprachen sind historisch gewachsen. Hierzu zählen z. B. Deutsch, Englisch und Französisch. Sie sind Ausdruck menschlichen Denkens, Fühlens und Wollens und weisen im Unterschied zu künstlichen Sprachen Mehrdeutigkeiten auf. • Künstliche Sprachen sind Zeichensysteme, die der Verständigung in einem eng begrenzten Fachgebiets dienen, zum Beispiel Programmiersprachen. Sprachen wie Esperanto sind ebenfalls künstliche Sprachen, die sich durch leichtere Schreibung und Grammatik gegenüber natürlichen Sprachen auszeichnen. • Formale Sprachen sind künstliche Sprachen, die mithilfe mathematischer Methoden definiert sind. 1.1 Sprachen und Paradigmen 4/38 Sprachklassen der Informatik Die Sprachen der Informatik werden typischerweise in zwei Klassen aufgeteilt: • General Purpose Language (GPL) • Domain Specific Language (DSL) Meistens zählt man die Programmiersprachen zu den GPLs und Sprachen für spezielle Anwendungen zu den DSLs. Die Klasseneinteilung ist nicht in allen Quellen genau identisch. Eine mögliche Beispiel-Einteilung finden Sie in einem Material der Veranstaltung. 1.1 Sprachen und Paradigmen 5/38 Sprachen der Informatik: Beispiele Um Sachverhalte mit Rechensystemen zu behandeln, müssen sie in eindeutigen – also künstlichen – Sprachen beschrieben werden. Einige Beispiele sollen dies verdeutlichen: • Algorithmen: Programmiersprachen (Java) • Dokumente: Markup-Sprachen (Html, XML), Seitenbeschreibungssprachen (Postscript) • Modelle, Systeme: Modellierungssprachen (UML), Spezifikationssprachen (Z) • Datenbanken: Anfragesprachen (SQL) • Mathematische Objekte: Symbolische Sprachen (Maple) • Abläufe: Simulationssprachen (GPSS) 1.1 Sprachen und Paradigmen 6/38 Sprachen der Informatik • In der Informatik hat man es mit einer Vielzahl von künstlichen Sprachen zu tun. • Sie alle beschreiben Sachverhalte in einem relativ kleinen Kontext, • dafür aber (hoffentlich) präzise, widerspruchsfrei und vollständig. In dieser Veranstaltung betrachten wir die grundlegenden Konzepte und Paradigmen moderner Programmiersprachen. Kenntnisse der imperativen und objektorientierten Programmierung sowie der Programmiersprache Java werden vorausgesetzt. 1.1 Sprachen und Paradigmen 7/38 Paradigmen höherer Programmiersprachen Jeder Programmiersprache liegt (mindestens) eine bestimmte Vorgehensweise der Formulierung zugrunde. Auf der Basis dieser Konzepte unterteilt man die höheren Programmiersprachen u. a. in folgende Kategorien: • Imperative Programmiersprachen, • Funktionale (applikative) Programmiersprachen, • Objektorientierte Programmiersprachen. • Prädikative (deduktive, logische) Programmiersprachen, Es gibt weitere Paradigmen, diese vier sind aber die am häufigsten erwähnten. 1.1 Sprachen und Paradigmen 8/38 Imperative Programmiersprachen • Bei diesen Sprachen besteht ein Programm aus einer Folge aus Befehlen an den Rechner. • Wesentlich ist das Variablenkonzept. Variable können verschiedene Werte annehmen. • Die Menge aller Variablen und ihrer Werte sowie der Programmzähler beschreiben den Zustand zu einem bestimmten Zeitpunkt. • Die Ausführung eines Programms bewirkt eine Zustandstransformation. 1.1 Sprachen und Paradigmen 9/38 Funktionale Programmiersprachen • Bei diesen Sprachen berechnen Programme Funktionen, die Eingabedaten auf Ausgabedaten abbilden. Ein funktionales Programm beschreibt die Beziehungen zwischen Ein- und Ausgabe mithilfe mathematischer Gleichungen. • Ausgehend von elementaren Ausdrücken werden die Beziehungen durch Ausdrücke steigender Komplexität festgelegt. • Das wichtigste Konstruktionsprinzip ist hierbei die Rekursion. • Außerdem spielen Funktionen höherer Ordnung, sogenannte Funktionale, eine wichtige Rolle. 1.1 Sprachen und Paradigmen 10/38 Objektorientierte Programmiersprachen • Bei diesen Sprachen werden alle zum Lösen eines Problems notwendigen Informationen als Objekte aufgefasst. Objekte besitzen Eigenschaften, die als Attribute bezeichnet werden. • Objekte können durch Nachrichten an andere Objekte Informationen austauschen. Dieser Vorgang kann zum Beispiel durch Aufruf von Methoden realisiert werden. • Gleichartige Objekte werden durch Klassen beschrieben. Von jeder Klasse können Objekte gemäß der Beschreibung erstellt und über die Methoden manipuliert werden. • Klassen/Objekte können untereinander in vielfältigen Beziehungen stehen. 1.1 Sprachen und Paradigmen 11/38 Prädikative Programmiersprachen • Bei diesen Sprachen wird Programmierung als Beweisen in einem System von Tatsachen und Schlussfolgerungen aufgefasst. • Der Anwender gibt eine Menge von Fakten und Regeln vor. • Die Aufgabe des Rechners ist es festzustellen, ob eine eingegebene Tatsache zutrifft oder nicht. • Alternativ können alle Fakten, die bestimmte Kriterien erfüllen, ermittelt werden. • Die Fakten werden durch Prädikate formuliert, den Schlussfolgerungen liegt ein logischer Kalkül zugrunde. Die prädikative Programmiersprache Prolog können Sie in der Vorlesung „Logik in der Informatik“ lernen. 1.1 Sprachen und Paradigmen 12/38 Paradigmen höherer Programmiersprachen Aus einer übergeordneten Sichtweise werden die folgenden Kategorien unterschieden: • Prozedurale Programmiersprachen: Es wird exakt angegeben, wie die Lösung eines Problems ermittelt werden kann. Imperative Programmiersprachen fallen in diese Kategorie. • Deklarative Programmiersprachen: Im Gegensatz zum prozeduralen Paradigma fragt man in der deklarativen Programmierung danach, was berechnet werden soll. Es wird also nicht der Lösungsweg programmiert, sondern angegeben, welches Ergebnis gewünscht ist. Deklarative Paradigmen beruhen auf mathematischen, rechnerunabhängigen Theorien. Beispiele hierfür sind prädikative und – bis zu einem gewissen Grade – auch funktionale Programmiersprachen. Ein hybrides Paradigma ist die Mischung von Paradigmen. 1.1 Sprachen und Paradigmen 13/38 Entwicklung der Programmiersprachen Edsger W. Dijkstra: „Jeder Programmierer weiß, dass es nur eine einzig wahre Programmiersprache gibt. Jede Woche eine neue.“ Albrecht Weinert: Java für Ingenieure, 2001, Seite 7: „Die Zahl der Programmiersprachen, die die Informatik in den letzten fünfzig Jahren hervorgebracht hat, ist Legion. Ernst zu nehmende Schätzungen sprechen von mehr als 20 000.“ Wenn Weinerts Schätzung zutrifft, sind es 7,7 Sprachen pro Woche! 1.1 Sprachen und Paradigmen 14/38 Entwicklung der Programmiersprachen JAVA 1995 93 91 SCHEME−Standard 89 87 C++ 85 83 OCCAM 81 ADA 79 LOGO 69 ALGOL68 67 61 59 SIMULA • Algol • Algol68 • Modula-2 PL/I • Scheme BASIC COBOL 57 1955 PROLOG PASCAL 71 63 CSP SCHEME C 73 65 SMALLTALK80 MODULA2 77 1975 Programmiersprachen in der Informatikausbildung ALGOL FORTRAN 1.1 Sprachen und Paradigmen LISP • Java 15/38 Paradigmen und Programmiersprachen Einige Programmiersprachen: imperativ: funktional: prädikativ: objektorientiert: hybrid: Algol, Algol68, Pascal, Ada, C, . . . Lisp, Scheme, ML, Haskell, . . . Prolog Smalltalk, Eiffel, . . . Java, C++, C# (imperativ, oo), Scala (imperativ, oo, funktional), . . . In der Regel lassen sich die Sprachen nicht eindeutig einem bestimmten Paradigma zuordnen. Zum Beispiel gibt es in Scheme Variable und Zuweisungen, d. h. imperative Konzepte. Java ist als „imperativ-basierte objektorientierte Programmiersprache“ (hybrides Paradigma) zu bezeichnen. C++ hingegen besitzt einen vollständigen imperativen Kern, während Smalltalk eine strikt objektorientierte Programmiersprache ist. 1.1 Sprachen und Paradigmen 16/38 Skriptsprachen • Bei Skriptsprachen handelt es sich um übergeordnete Sprachen, um vorhandene Programme oder Prozeduren kontrolliert ablaufen zu lassen. • Skriptsprachen haben ihren Ursprung in den Kommandosprachen (Job Control Language, JCL) von Betriebssystemen. • Einfache Skriptsprachen sind die Shell-Skripts von Unix. Mächtigere Skriptsprachen sind beispielsweise Perl, PHP, Python oder JavaScript. • Skriptsprachen werden in der Regel interpretiert, nicht kompiliert. 1.1 Sprachen und Paradigmen 17/38 Datenstrukturen und Typsysteme Programmiersprachen bieten die Möglichkeit, aus elementaren Datenbereichen mithilfe von Konstruktoren komplexe Datenbereiche aufzubauen. Datenbereiche werden häufig Datenstrukturen genannt. • Elementare Datenbereiche ◦ boolean, char, cardinal, integer, real, enumeration, . . . • Wertebereiche, Operationen • Konstruktoren ◦ array, record, set, pointer, . . . • Typäquivalenz, Typanpassung, Typkompabilität, . . . Alle Aspekte, die die Datenbereiche einer Programmiersprache betreffen, werden als deren Typsystem bezeichnet. 1.1 Sprachen und Paradigmen 18/38 Paradigmenübergreifende Konzepte (Auswahl) Die folgende Liste enthält einige paradigmenübergreifende Konzepte. Nicht jeder Punkt ist für jedes Paradigma relevant. Beispielsweise benötigt die prädikative Sprache Prolog keine Ablaufsteuerung. • Ablaufsteuerung • Unterprogramme, Module, abstrakte Datentypen • Ausnahme- und Ereignisbehandlung • Annotationen • Programmierung randomisierter Algorithmen • Programmierung nichtdeterministischer Algorithmen • Programmierung paralleler und verteilter Algorithmen • GUI-, Echtzeit-, Netzwerk-, Datenbank-, ... -programmierung 1.1 Sprachen und Paradigmen 19/38 Prinzipien des Sprachentwurfs • Effizienz • Allgemeingültigkeit • Orthogonalität • Uniformität • Einfachheit • Ausdruckskraft • Genauigkeit • Maschinenunabhängigkeit • Sicherheit • Konsistenz mit anerkannten Konventionen • Erweiterbarkeit • Einschränkbarkeit 1.1 Sprachen und Paradigmen 20/38 1. Einführung 1.1 Sprachen und Paradigmen 1.2 Definition von Programmiersprachen 1.3 Implementierung von Programmiersprachen 1.2 Definition von Programmiersprachen 21/38 Definition von Programmiersprachen Bestandteile einer Programmiersprache: • Lexik, • Syntax, • Semantik. Die Pragmatik einer Programmiersprache untersucht ihre Anwendbarkeit und Nützlichkeit. Sie gehört nicht zur Definition der Sprache. 1.2 Definition von Programmiersprachen 22/38 Lexik Im Lexikon findet man: • Lexem (griechisch) das, Sprachwissenschaft: kleinste semantische Einheit, Träger der lexikalischen Bedeutung; das Lexem tritt als Einzelwort (z. B. Wald), als Teil eines Wortes (z. B. wald- in waldig) und als Wortverbindung auf (z. B. Waldbrand). • Lexik die, der Wortschatz einer Sprache. Die Lexik einer Programmiersprache bestimmt die textuellen Grundbausteine der Programme. Solche Bausteine sind etwa Schlüsselwörter und Bezeichner. Sie werden z. B. durch Aufzählung oder reguläre Ausdrücke angegeben. Lexeme einer Programmiersprache können aus mehr als einem Zeichen bestehen. In Programmiersprachen werden Lexeme auch Token genannt. 1.2 Definition von Programmiersprachen 23/38 Syntax • Die Syntax einer Programmiersprache beschreibt, wie aus den Grundbausteinen vollständige Programme gebildet werden können. • In den meisten Fällen wird die Syntax durch eine kontextfreie Grammatik festgelegt. • Eine kontextfreie Grammatik G = (VN , VT , P, S) besteht aus einem Nichtterminalalphabet VN , einem Terminalalphabet VT , einer Produktionenmenge P und dem Startsymbol S. Mit L(G) bezeichnen wir die von der kontextfreien Grammatik G erzeugte Sprache. • Kontextfreie Grammatiken können durch Syntaxdiagramme grafisch dargestellt werden. 1.2 Definition von Programmiersprachen 24/38 Syntax • Programmiersprachen sind i. Allg. kontextsensitiv und nicht kontextfrei. • Beispielsweise kann durch eine kontextfreie Grammatik nicht ausgedrückt werden, dass jeder Bezeichner vor seiner Benutzung deklariert werden muss. • Andere Formalismen, zum Beispiel zweischichtige oder attributierte Grammatiken, ermöglichen es, kontextsensitive Aspekte in die Definition der Syntax einer Programmiersprache einzubeziehen. 1.2 Definition von Programmiersprachen 25/38 Semantik • Die Bedeutung der syntaktisch korrekten Programme ist durch die Semantik der Sprache gegeben. Sie kann beispielsweise mithilfe von Zuständen definiert werden. Man spricht dann von einer operationellen Semantik. • In der denotationalen Semantik werden den syntaktischen Einheiten Funktionen zugeordnet. Hier spielen vollständige Halbordnungen, stetige Abbildungen und Fixpunkte eine wichtige Rolle. • Weitere Möglichkeiten sind die axiomatische Semantik (Programmverifikation, Hoare-Kalkül, Spezifikationssprachen) und die algebraische Semantik. Näheres erfahren Sie in der Veranstaltung „Semantik von Programmiersprachen“. 1.2 Definition von Programmiersprachen 26/38 Beispiel: Lexik • Eine Programmiersprache enthält endlich viele Schlüsselwörter. Sie können durch Aufzählung angegeben werden: if, then, else, begin, end, while, do, od, ... • Die Menge der Bezeichner einer Programmiersprache kann zum Beispiel durch die folgenden regulären Ausdrücke definiert werden: Ziffer = {0, 1, ..., 9} Buchstabe = {a, b, c, ..., y , z} Bezeichner = Buchstabe · (Buchstabe | Ziffer)∗ 1.2 Definition von Programmiersprachen 27/38 Beispiel: Syntax Die folgenden Zeilen enthalten einige Produktionen einer kontextfreien Grammatik G zur Beschreibung einer kleinen Programmiersprache in der sog. Backus-Naur-Form: <Anweisungsfolge> ::= <Anweisung> ; <Anweisungsfolge> | <Anweisung> <Anweisung> ::= <Zuweisung> | <While-Anweisung> | ... <Zuweisung> ::= <Bezeichner> := <arithmetischer Ausdruck> <While-Anweisung> ::= while <logischer Ausdruck> do <Anweisungsfolge> od 1.2 Definition von Programmiersprachen 28/38 Beispiel: Operationelle Semantik • Zunächst werden Zustände z ∈ Z definiert. Hierbei kann es sich um die Zustände eines abstrakten Automaten handeln. In unserem Beispiel ist Z = {z | z : V → Z}, wobei V die Menge aller Variablen ist. • Die Semantik ordnet jedem syntaktisch korrektem Programm P ∈ L(G) eine partielle Funktion (Zustandstransformation) M [P ] : Z − →Z p als seine Bedeutung zu. M hat also die Gestalt M : L → (Z − → Z ). p Neben dem operationellen Ansatz gibt es weitere (s. Vorlesung „Semantik von Programmiersprachen“). 1.2 Definition von Programmiersprachen 29/38 Beispiel: Algorithmus von Euklid Der folgende in der obigen imperativen Sprache formulierte Algorithmus von Euklid (ca. 300 v. Chr.) berechnet den größten gemeinsamen Teiler der Zahlen x , y ∈ N mit x ≥ 0 und y > 0: a := x; b := y; while b do r := a := b := od # 0 a mod b; b; r Nach Ausführung des Programmfragments gilt a = ggT(x , y ). 1.2 Definition von Programmiersprachen 30/38 Beispiel: Algorithmus von Euklid Es seien x = 36 und y = 52: Variable r a b z0 – – – z1 – 36 – z2 – 36 52 z5 36 52 36 z8 16 36 16 z11 4 16 4 z14 0 4 0 Das Zeichen – bedeutet, dass der Wert dieser Variablen im betreffenden Zustand irrelevant ist. ggT(36, 52) = 4 Durchlaufene Zustände: z0 , z1 , z2 , ... , z14 ∈ Z Es gilt M [P ](z0 ) = z14 . 1.2 Definition von Programmiersprachen 31/38 Sprachreport • Die Definition einer Programmiersprache erfolgt oft in Form eines sog. Sprachreports. • Dieser enthält in der Regel eine kontextfreie Grammatik zur Beschreibung der Lexik und der Syntax. Die Produktionen werden häufig in der Backus-Naur-Form oder einer Varianten davon angegeben. • Darüber hinaus werden die Produktionen meistens grafisch in der Form eines Syntaxdiagramms veranschaulicht. • Die Semantik wird in der Regel informell, in einigen Fällen auch halbformal oder sogar formal definiert. 1.2 Definition von Programmiersprachen 32/38 1. Einführung 1.1 Sprachen und Paradigmen 1.2 Definition von Programmiersprachen 1.3 Implementierung von Programmiersprachen 1.3 Implementierung von Programmiersprachen 33/38 Klassifikation der Programmiersprachen Die Programmiersprachen lassen sich grob in drei Klassen einteilen: • Maschinensprachen Bits und Bytes, für den menschlichen Leser kaum verständlich • Maschinenorientierte Sprachen (Assembler) stellen die Befehle in einem Mnemo-Code dar ADDIC 23, R0 STO R0, #12004 • Problemorientierte Sprachen imperative, funktionale, objektorientierte, logische Sprachen, Spezialsprachen Ein Computer versteht nur Maschinensprachen! 1.3 Implementierung von Programmiersprachen 34/38 Implementierung von Programmiersprachen Compiler übersetzen Quellprogramme aus problemorientierten Sprachen in äquivalente Zielprogramme in Maschinensprachen: cc -o prog prog.c prog input output Interpreter lesen das Programm zusammen mit den Eingabedaten ein und führen es aus: scm prog.scm input output Mischverfahren übersetzen das Programm zunächst mit einem Compiler in eine Zwischensprache. Das übersetzte Programm wird anschließend interpretiert: javac prog.java java prog input output 1.3 Implementierung von Programmiersprachen 35/38 Implementierung von Programmiersprachen Interpreter müssen das Programm bei jedem Lauf erneut analysieren. Dies bedeutet einen gewissen Effizienzverlust. Typisch, aber nicht zwingend: • Compiler: C • Interpreter: Scheme • Mischverfahren: Java • Mehrere Angebote gleichzeitig: z. B. Haskell (ghc, ghci) Näheres zu Aufbau und Arbeitsweise dieser Programme erfahren Sie in den Veranstaltungen „Compiler I“, „Compiler II“ sowie im „Compilerbaupraktikum“. 1.3 Implementierung von Programmiersprachen 36/38 Verarbeitung von Java-Programmen Java−Quellprogramm javac Java−Bytecode java VM für Windows java VM für Linux • Zuerst wird ein Quellprogramm vom Compiler in Bytecode übersetzt. 1.3 Implementierung von Programmiersprachen • Im zweiten Schritt wird der Bytecode vom Interpreter ausgeführt. Der Bytecode kann als Maschinencode der sogenannten virtuellen Java-Maschine (JVM) angesehen werden. Bytecode ist portabel. • Der Compiler ist maschinenunabhängig, der Interpreter muss für jede Plattform neu entwickelt werden. 37/38 Verarbeitung von Java-Programmen • Interpretierter Code ist langsamer in der Ausführung als kompilierter Code, selbst wenn dieser als Bytecode vorliegt. • Prinzipiell können Java-Programme auch in Maschinensprache übersetzt werden. Dann geht allerdings die Portierbarkeit verloren. • Eine Alternativlösung bieten Just-in-Time-Compiler (JIT). Ein JIT ist ein Programm, das den Bytecode einzelner Methoden während der Ausführung in Maschinencode der jeweiligen Plattform übersetzt. So kann die Methode beim nächsten Aufruf deutlich schneller ausgeführt werden. Vorteilhaft ist, dass der Bytecode nicht verändert wird und damit das übersetzte Programm portabel bleibt. 1.3 Implementierung von Programmiersprachen 38/38