3. Funktionales Programmieren

Ähnliche Dokumente
Beispiele: (Funktionen auf Listen) (3) Bemerkungen: Die Datenstrukturen der Paare (2) Die Datenstrukturen der Paare

Was bisher geschah. deklarative Programmierung. funktionale Programmierung (Haskell):

Grundprinzipien der funktionalen Programmierung

Funktionale Programmiersprachen

Funktionale Programmierung

Programmierung und Modellierung

2.5 Listen. Kurzschreibweise: [42; 0; 16] Listen werden mithilfe von [] und :: konstruiert.

Prinzipielle Ausführungsvarianten I

Vorsicht bei redundanten und unvollständigen Matches!

Strukturelle Rekursion und Induktion

Haskell, Typen, und Typberechnung. Grundlagen der Programmierung 3 A. Einige andere Programmiersprachen. Typisierung in Haskell

Kapitel 3: Variablen

JAVA-Datentypen und deren Wertebereich

Frage, Fragen und nochmals Fragen

Programmieren I. Kapitel 5. Kontrollfluss

Programmieren in Haskell Programmiermethodik

Crashkurs Haskell Mentoring WiSe 2016/17. Anja Wolffgramm Freie Universität Berlin

Einführung in die Programmierung mit VBA

Typdeklarationen. Es gibt in Haskell bereits primitive Typen:

Tag 7. Pattern Matching und eigene Datentypen

Zweite Möglichkeit: Ausgabe direkt auf dem Bildschirm durchführen:

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme

2.5 Primitive Datentypen

TECHNISCHE UNIVERSITÄT MÜNCHEN FAKULTÄT FÜR INFORMATIK

ALP I Einführung in Haskell

Spezifikation der zulässigen Parameter. Bemerkungen: Bemerkungen: (2) Design by Contract:

Java Einführung VARIABLEN und DATENTYPEN Kapitel 2

Algorithmen & Programmierung. Ausdrücke & Operatoren (1)

Java Einführung Operatoren Kapitel 2 und 3

Programmierung und Modellierung

Funktionale Programmierung

VBA-Programmierung: Zusammenfassung

Datentypen. Agenda für heute, 4. März, Pascal ist eine streng typisierte Programmiersprache

1 Mengen. 1.1 Elementare Definitionen. Einige mathematische Konzepte

CGI Programmierung mit Ha. Markus Schwarz

Einführung in die Informatik 1

Elementare Mengenlehre

Der Datentyp String. Stringvariable und -vergleiche

Einführung Datentypen Verzweigung Schleifen. Java Crashkurs. Kim-Manuel Klein May 4, 2015

Praktische Informatik 3: Einführung in die Funktionale Programmierung Vorlesung vom : Rekursive Datentypen

Praktische Informatik 3: Funktionale Programmierung Vorlesung 3 vom : Algebraische Datentypen

Allgemeine Hinweise: TECHNISCHE UNIVERSITÄT MÜNCHEN. Name Vorname Studiengang Matrikelnummer. Hörsaal Reihe Sitzplatz Unterschrift

Java 8. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Oktober 2014 JAV8

Vorsemesterkurs Informatik

1 Syntax von Programmiersprachen

Kontrollstrukturen, Pseudocode und Modulo-Rechnung

Funktionale Programmierung mit Haskell

Weitere Operationen auf neu deklarierten Typen: Begriffsklärung: Weitere Anwendungen der datatype-deklaration: Beispiel: (Aufzählungstypen)

Programmieren in Haskell

1 Potenzen und Polynome

Programmierung WS12/13 Lösung - Übung 1 M. Brockschmidt, F. Emmes, C. Otto, T. Ströder

Javaprogrammierung mit NetBeans. Variablen, Datentypen, Methoden

Einfache Ausdrücke Datentypen Rekursive funktionale Sprache Franz Wotawa Institut für Softwaretechnologie

Pass by Value Pass by Reference Defaults, Overloading, variable Parameteranzahl

Eine Baumstruktur sei folgendermaßen definiert. Eine Baumstruktur mit Grundtyp Element ist entweder

Elementare Konzepte von

C.3 Funktionen und Prozeduren

Sprachen und Programmiersprachen

II. Grundlagen der Programmierung. 9. Datenstrukturen. Daten zusammenfassen. In Java (Forts.): In Java:

String s1, s2; Eine Zuweisung geschieht am einfachsten direkt durch Angabe des Strings eingeschlossen in doppelte Hochkommata:

Einführung Datentypen Verzweigung Schleifen Funktionen Dynamische Datenstrukturen. Java Crashkurs. Kim-Manuel Klein

Kapitel 9: Pattern Matching

Repetitorium Informatik (Java)

Dr. Monika Meiler. Inhalt

Die Programmiersprache C Eine Einführung

Methoden und Funktionen in Scala

Zahlen in Haskell Kapitel 3

Java Kurs für Anfänger Einheit 5 Methoden

Grundlagen der Programmierung Prof. H. Mössenböck. 6. Methoden

Formale Methoden in der Informatik Wiederholung klassische Logik Konkrete Datentypen (algebraische Strukturen) Abstrakte Datentypen

Objektorientierte und Funktionale Programmierung

Um nicht immer den Strukturnamen verwenden zu müssen, kann man auch alle Definitionen einer Struktur auf einmal sichtbar machen:

Haskell zur Constraint-Programmierung HaL8

Mathematik für Informatiker I

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

4. Induktives Definieren - Themenübersicht

Einführung in die Informatik für Hörer aller Fakultäten II. Andreas Podelski Stephan Diehl Uwe Waldmann

Einführung in die Informatik I (autip)

4.Grundsätzliche Programmentwicklungsmethoden

Programmierung 1 Probeklausur zur Vorklausur

1. Der Begriff Informatik 2. Syntax und Semantik von Programmiersprachen. I.2. I.2. Grundlagen von von Programmiersprachen.

Der λ-kalkül. Frank Huch. Sommersemester 2015

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

Algorithmen & Programmierung. Steuerstrukturen im Detail Selektion und Iteration

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008

Einstieg in die Informatik mit Java

Übungen zur Vorlesung Wissenschaftliches Rechnen I. Grundelemente von Java. Eine Anweisung. wird mit dem Wertzuweisungsoperator = geschrieben.

Welche Informatik-Kenntnisse bringen Sie mit?

Operatoren und Ausdrücke

Programmierung mit C Zeiger

Elementare Datentypen in C++

Vorsemesterkurs Informatik

Einführung in die Informatik

Grundlagen der Informatik I (Studiengang Medieninformatik)

Theoretische Informatik. Reguläre Sprachen und Automaten

Einstieg in die Informatik mit Java

Algorithmen und Programmierung

Einführung in den Einsatz von Objekt-Orientierung mit C++ I

Formale Grundlagen 2008W. Vorlesung im 2008S Institut für Algebra Johannes Kepler Universität Linz

Transkript:

3. Funktionales Programmieren Grundkonzepte funktionaler Programmierung Algorithmen auf Listen und Bäumen Abstraktion mittels Polymorphie und Funktionen höherer Ordnung Semantik, Testen und Verifikation 78

Inhalt von Kapitel 3: 1. Einführung 2. Grundkonzepte von Softwaresystemen 3. Funktionales Modellieren und Programmieren 3.1 Grundkonzepte funktionaler Programmierung 3.1.1 Zentrale Begriffe und Einführung 3.1.2 Rekursive Funktionen 3.1.3 Listen und Tupel 3.1.4 Benutzerdefinierte Datentypen 3.1.5 Felder in ML 3.1.6 Signaturen und Strukturen 3.2 Algorithmen auf Listen und Bäumen 3.2.1 Sortieren 3.2.2 Suchen 3.3 Abstraktion mittels Polymorphie und Funktionen höherer Ordnung 3.3.1 Typisierung 3.3.2 Funktionen höherer Ordnung 3.4 Semantik, Testen und Verifikation 3.4.1 Zur Semantik funktionaler Programme 3.4.2 Testen und Verifikation 4. Prozedurales Programmieren 5. Objektorientiertes Programmieren 79

3.1 Grundkonzepte funktionaler Programmierung Vorgehen: Zentrale Begriffe und Einführung: Funktion, Wert, Typ, Datenstruktur, Auswertung,... Rekursive Funktionen Listen und Tupel Benutzerdefinierte Datentypen 3.1.1 Zentrale Begriffe und Einführung Funktionale Programmierung im Überblick: Funktionales Programm: - partielle Funktionen von Eingabe- auf Ausgabedaten - besteht aus Deklarationen von (Daten-)Typen, Funktionen und (Daten-)Strukturen - Rekursion ist eines der zentralen Sprachkonzepte - in Reinform: kein Zustandskonzept, keine veränderlichen Variablen, keine Schleifen, keine Zeiger Ausführung eines funktionalen Programms: Anwendung der Funktion auf Eingabedaten 80

Definition: (partielle Funktion) Ein Funktion heißt partiell, wenn sie nur auf einer Untermenge ihres Argumentbereichs definiert ist. Andernfalls heißt sie total. Definition: (Funktionsanwendung, -auswertung, Terminierung, Nichtterminierung) Bezeichne f eine Funktion und a ein zulässiges Argument von f. Die Anwendung von f auf a nennen wir eine Funktionsanwendung (engl. function application); üblicherweise schreibt man dafür f(a) oder f a. Den Prozess der Berechnung des Funktionswerts nennen wir Auswertung (engl. evaluation). Die Auswertung kann: - nach endlich vielen Schritten terminieren und ein Ergebnis liefern (normale Terminierung, engl. normal termination), - nach endlich vielen Schritten terminieren und einen Fehler melden (abrupte Terminierung, engl. abrupt termination), - nicht terminieren, d.h. der Prozess der Auswertung kommt (von alleine) nicht zu Ende. 81

Bemerkungen: Entsprechendes gilt in anderen Programmierparadigmen. Da Terminierung nicht entscheidbar ist, benutzt man in der Informatik häufig partielle Funktionen. Beispiel: McCarthy s Funktion: (Zur Entscheidbarkeit der Terminierung) Bezeiche N + die Menge der positiven ganzen Zahlen und sei m : N + N + wie folgt definiert: n -10, für n >100 m( n ) = m( m( n+11) ), für n 100 Ist m für alle Argumente wohldefiniert? Durch Einführen eines Elements undefiniert kann man jede partielle Funktion total machen. Üblicherweise bezeichnet man das Element für undefiniert mit (engl. bottom ). 82

Begriffsklärung: (Wert, Value) Werte (engl. values) in der (reinen) funktionalen Programmierung sind - Elementare Daten (Zahlen, Wahrheitswerte, Zeichen,...), - zusammengesetzte Daten (Listen von Werten, Wertepaare,...), - (partielle) Funktionen mit Werten als Argumenten und Ergebnissen. Bemerkungen: In anderen Sprachparadigmen gibt es auch Werte, allerdings werden Funktionen nicht immer als Werte betrachtet. (z.b. in der objektorientierten Programmierung: immutable objects ) Im Mittelpunkt der funktionalen Programmierung steht die Definition von Wertemengen (Datentypen) und Funktionen. Funktionale Programmsprachen stellen dafür Sprachmittel zur Verfügung. 83

Wie für abstrakte Objekte oder Begriffe typisch, besitzen Werte - keinen Ort, - keine Lebensdauer, - keinen veränderbaren Zustand, - kein Verhalten. Begriffsklärung: (Typ, type) Typisierte Sprachen besitzen ein Typsystem. Ein Typ (engl. type) fasst Werte zusammen, auf denen die gleichen Funktionsanwendungen zulässig sind. In typisierten Sprachen besitzt jeder Wert einen Typ. In funktionalen Programmiersprachen gibt es drei Arten von Werten bzw. Typen, mit denen man rechnen kann: Basisdatentypen ( int, bool, string,... ) benutzerdef., insbesondere rekursive Datentypen Funktionstypen ( z.b. int bool oder ( int int ) ( int int ) ) 84

Datenstrukturen Eine Struktur fasst Typen und Werte zusammen, insbesondere also auch Funktionen. Datenstrukturen sind Strukturen, die mindestens einen neuen Datentyp und alle seine wesentlichen Funktionen bereitstellen. Eine Datenstruktur besteht aus einer oder mehrerer disjunkter Wertemengen zusammen mit den darauf definierten Funktionen. In der Mathematik nennt man so ein Gebilde eine Algebra oder einfach nur Struktur. In der Informatik spricht man auch von einer Rechenstruktur. 85

Definition: (Datenstruktur mit Signatur) Eine Signatur einer Datenstruktur (T,F) besteht aus - einer endlichen Menge T von Typbezeichnern und - einer endlichen Menge F von Funktionsbezeichnern, wobei für jedes f F ein Funktionstyp f : T1 *... * Tn T0, Ti T, 0 i n, 0 n, definiert ist. n gibt die Stelligkeit von f an. Eine (partielle) Datenstruktur mit Signatur (T,F) ordnet - jedem Typbezeichner T T eine Wertemenge, - jedem Funktionsbezeichner f F eine partielle Funktion zu, so dass Argument- und Wertebereich von f den Wertemengen entsprechen, die zu f s Funktionstyp gehören. 86

Bemerkungen: Wir betrachten zunächst die Basisdatenstrukturen, wie man sie in jeder Programmier-, Spezifikationsund Modellierungssprache findet. Die Basisdatenstrukturen (engl. basic / primitive data structures) bilden die Grundlage zur Definition weiterer Typen, Funktionen und Datenstrukturen. Als Beispiel dienen uns die Basisdatenstrukturen der funktionalen Sprache ML. Später lernen wir auch die Basisdatenstrukturen von Java kennen. Wir benutzen Funktionsbezeichner auch mit Infix-Schreibweise. Funktionen ohne Argumente nennen wir Konstanten. Allgemeinere Formen von Strukturen betrachten wir erst am Ende des Kapitels. 87

Die Datenstruktur Boolean: Typ: bool Funktionen: = : bool * bool bool <> : bool * bool bool not: bool bool Konstanten: true: bool false: bool Dem Typbezeichner bool ist die Wertemenge { true, false } zugeordnet. = bezeichnet die Gleichheit auf Wahrheitswerten <> bezeichnet die Ungleichheit auf Wahrheitsw. not bezeichnet die logische Negation. true bezeichnet den Wert true. false bezeichnet den Wert false. 88

Bemerkung: Im Folgenden unterscheiden wir nur noch dann zwischen Funktionsbezeichner und bezeichneter Funktion, wenn dies aus Gründen der Klarheit nötig ist. Die Datenstruktur Int: Die Datenstruktur Int erweitert die Datenstruktur Boolean, d.h. sie umfasst den Typ bool und die darauf definierten Funktionen. Zusätzlich enthält sie: Typ: int Funktionen: = : int * int bool (* Gleichheit *) <>: int * int bool (* Ungleichheit *) ~ : int int (* Negation *) + : int * int int (* Addition *) - : int * int int (* Subtraktion *) * : int * int int (* Multiplikation *) abs: int int (* Absolutbetrag *) 89

div: int * int int (* ganzzahlige Division *) mod: int * int int (* Divisionsrest *) < : int * int bool (* kleiner *) > : int * int bool (* größer *) <=: int * int bool (* kleiner gleich *) >=: int * int bool (* größer gleich *) Konstanten: - in Dezimaldarstellung: 0, 128, ~245 - in Hexadezimaldarstellung: 0x0, 0x80, ~0xF5 Dem Typbezeichner int ist in ML eine rechnerabhängige Wertemenge zugeordnet, typischerweise die Menge der ganzen Zahlen von -2^30 bis 2^30-1. Innerhalb der Wertemenge sind die Funktionen der Datenstruktur Int verlaufsgleich mit den üblichen Funktionen auf den ganzen Zahlen. Ausserhalb der Wertemenge sind sie nicht definiertt. Insbesondere bezeichnen +, *, abs, ~ partielle Funktionen. 90

Begriffsklärung: Wenn unterschiedliche Funktionen oder andere Programmelemente den gleichen Bezeichner haben, spricht man vom Überladen des Bezeichners (engl. Overloading). Beispiel: (Überladung von Bezeichnern) Funktionsbezeichner können überladen werden, d.h. in Abhängigkeit vom Typ ihrer Argumente bezeichnen sie unterschiedliche Funktionen. Die Datenstruktur Real: Die Datenstruktur Real erweitert die Datenstruktur Int, d.h. sie umfasst deren Typen und Funktionen sowie: Typ: real Funktionen: ~ : real real (* Negation *) + : real * real real (* Addition *) - : real * real real (* Subtraktion *) * : real * real real (* Multiplikation *) / : real * real real (* Division *) abs: real real (* Absolutbetrag *) 91

< : real * real bool (* kleiner *) > : real * real bool (* größer *) <=: real * real bool (* kleiner gleich *) >=: real * real bool (* größer gleich *) real : int real (* nächste reelle Zahl *) round: real int (* nächste ganze Zahl *) floor: real int (* größte ganze Zahl <= *) ceil : real int (* kleinste ganze Zahl >= *) trunc: real int (* ganzzahliger Anteil *) Konstanten: - mit Dezimalpunkt: 0.0, 1000.0, 128.9, ~2.897 - mit Exponenten: 0e0, 1E3, 1289E~1, ~2897e~3 Dem Typbezeichner real ist in ML eine rechnerabhängige Wertemenge zugeordnet. Entsprechendes gilt für die präzise Semantik der Funktionsbezeichner. Bemerkung: Die ganzen Zahlen sind in der Programmierung keine Teilmenge der reellen Zahlen! Keine Gleichheitsoperationen auf reellen Zahlen. 92

Die Datenstruktur Char: Die Datenstruktur Char erweitert die Datenstruktur Int, also auch Bool. Zusätzlich enthält sie: Typ: char Funktionen: = : char * char bool <>: char * char bool < : char * char bool (* kleiner *) > : char * char bool (* größer *) <=: char * char bool >=: char * char bool chr: int char (* char zu ASCII Code *) ord: char int (* ASCII Code zu char *) Konstanten: Konstantenbezeichner haben die Form # α,wobei α - ein druckbares Zeichen ist, - die Form \t, \n, \\, \,... hat, - die Form \zzz hat, wobei z eine Ziffer ist und die dargestellte Dezimalzahl ein legaler Zeichencode ist. 93

Die Datenstruktur String: Die Datenstruktur String erweitert die Datenstruktur Char, d.h. sie umfasst deren Typen und Funktionen. Zusätzlich enthält sie: Typ: string Funktionen: = : string * string bool <>: string * string bool < : string * string bool (* kleiner *) > : string * string bool (* größer *) <=: string * string bool >=: string * string bool ^ : string * string string (* Zeichenreihenkonkatenation *) size: string int (* Länge *) substring: string * int * int string (* Teilzeichenreihe *) str: char string (* entsprechende Zeichenreihe der Länge 1 *) 94

Konstanten: Zeichenreihen eingeschlossen in doppelte Hochkommas: Ich bin ein String!! Ich \098\105\110 ein String!! Mein Lehrer sagt: \ Nehme die Dinge genau!\ String vor Zeilenumbruch\n Nach Zeilenumbruch Dem Typbezeichner string ist die Menge der Zeichenreihen über der Menge der Zeichen zugeordnet, die von der vorliegenden ML-Implementierung unterstützt werden. Den Vergleichsoperationen liegt die lexikographische Ordnung zugrunde, wobei die Ordnung auf den Zeichen auf derem Code basiert (siehe Datenstruktur Char). 95

Bemerkung: Es wird unterschieden zwischen Zeichen und Zeichenreihen der Länge 1. Aufbauend auf der Datenstruktur string und der Listendatenstruktur stellt ML auch die Funktionen explode : string char list implode : char list string zur Verfügung. Jede Programmier-, Modellierungs- und Spezifikationssprache besitzt Basisdatenstrukturen. Die Details variieren aber teilweise deutlich. Wenn Basisdatenstrukturen implementierungsoder rechnerabhängig sind, entstehen Portabilitätsprobleme. Der Trend bei den Basisdatenstrukturen geht zur Standardisierung. 96

Aufbau funktionaler Programme Im Kern, d.h. wenn man die Modularisierungskonstrukte nicht betrachtet, bestehen funktionale Programme aus: der Beschreibung von Werten: - z.b. (7+23), 30 Vereinbarung von Bezeichnern für Werte (einschließlich Funktionen): - val x = 7; der Definitionen von Typen: - type t =... ; - datatype dt =... ; Im Folgenden betrachten wir die Sprache ML. ML bietet ein interaktives Laufzeitsystem, das Eingaben obiger Form akzeptiert. Selbstverständlich kann man Eingaben auch aus einer Datei einlesen. Darüber hinaus gibt es Übersetzer für ML. 97

Beschreibung von Werten: - mittels Konstanten oder Bezeichnern für Werte: 23 Ich bin eine Zeichenreihe true x - durch direkte Anwendung von Funktionen: abs(~28382) Urin ^ stinkt not(true) - durch geschachtelte Anwendung von Funktionen: floor (~3.4) = trunc (~3.4) substring ( Urin ^ stinkt, 0,3) - durch Verwendung der nicht-strikten Operationen: if <boolausdruck> then <Ausdruck> else <Ausdruck> <boolausdruck> andalso <boolausdruck> <boolausdruck> orelse <boolausdruck> 98

Begriffsklärung: (Ausdruck, expression) Ausdrücke sind das Sprachmittel zur Beschreibung von Werten. Ein Ausdruck (engl. expression) in ML ist - eine Konstante, - ein Bezeichner (Variable, Name), - die Anwendung einer Funktion auf einen Ausdruck, - ein nicht-strikter Ausdruck gebildet mit den Operationen if-then-else, andalso oder orelse, - oder ist mit Sprachmitteln aufgebaut, die erst später behandelt werden. Jeder Ausdruck hat einen Typ: - Der Typ einer Konstanten ergibt sich aus der Signatur. - Der Typ eines Bezeichners ergibt sich aus dem Wert, den er bezeichnet. - Der Typ einer Funktionsanwendung ist der Ergebnistyp der Funktion. - Der Typ eines if-then-else-ausdrucks ist gleich dem Typ des Ausdruck im then- bzw. else-zweig. Der Typ der anderen nicht-strikten Ausdrücke ist bool. 99

Präzedenzregeln: Wenn Ausdrücke nicht vollständig geklammert sind, ist im Allg. nicht klar, wie ihr Syntaxbaum aussieht. Beispiele: 3 = 5 = true false = true orelse true false andalso true orelse true Präzedenzregeln legen fest, wie Ausdrücke zu strukturieren sind: - Am stärksten binden Funktionsanwendungen in Präfixform. - Regeln für Infix-Operationen: infix 7 *, /, div, mod; infix 6 +, -; infix 4 = <> < > <= >=; Je höher die Präzedenzzahl, desto stärker binden die Operationen. - andalso bindet stärker als orelse. - Binäre Operationen sind linksassoziativ (d.h. sie werden von links her geklammert). - Vergleichsoperationen binden stärker als nichtstrikte Operationen. 100

Deklaration und Bezeichnerbindung: Bisher haben wir Ausdrücke formuliert, die sich auf die vordefinierten Funktions- und Konstantenbezeichner von ML gestützt haben. Syntaktisch gesehen, heißt Programmierung: - neue Typen, Werte und Funktionen zu definieren, - die neu definierten Elemente unter Bezeichnern zugänglich zu machen. Begriffsklärung: (Vereinbarung, Deklaration) In Programmiersprachen dienen Vereinbarungen oder Deklarationen (engl. declaration) dazu, den in einem Programm verwendeten Elementen Bezeichner/Namen zu geben. Dadurch entsteht eine Bindung (n,e) zwischen dem Bezeichner n und dem bezeichneten Programmelement e. An allen Programmstellen, an denen die Bindung sichtbar ist, kann der Bezeichner benutzt werden, um sich auf das Programmelement zu beziehen. 101

Bemerkung: Die Art der Programmelemente, die in Deklarationen vorkommen können, hängt von der Programmiersprache ab. In ML sind es im Wesentlichen Typen und Werte. Die Regeln, die die Sichtbarkeit von Bindungen bzw. Bezeichern festlegen, sind ebenfalls sprachabhängig und können sehr komplex sein. Wir führen die Sichtbarkeitsregeln schrittweise ein. In Folgenden betrachten wir drei Arten von ML-Vereinbarungen: 1. Wertvereinbarungen 2. Vereinbarungen (rekursiver) Funktionen 3. Vereinbarungen benutzerdeklarierter Typen 102

Wertvereinbarungen: haben (u.a.) die Form: val <Bezeichner> = <Ausdruck> ; val <Bezeichner> : <Typ> = <Ausdruck> ; In der ersten Form wird der Typ des Ausdrucks zum Typ des Bezeichners, in der zweiten Form muss der Typ des Ausdrucks gleich dem vereinbarten Typ sein. Der rechtsseitige Ausdruck darf nur Bezeichner enthalten, die vorher oder im Ausdruck selbst gebunden wurden. Beispiele: (Wertvereinbarungen) val sieben : real = 7.0 ; val dkv = 3.4 ; val flag = floor ( ~dkv ) = trunc (~dkv) ; val dkv = Deutscher Komiker Verein e.v. ; 103

Funktionsvereinbarungen: Zwei Probleme: 1. bisher keine Ausdrücke, die eine Funktion als Ergebnis liefern (Ausnahme: Funktionsbezeichner) 2. Funktionen können rekursiv sein, d.h. der Funktionsbezeichner kommt im definierenden Ausdruck vor. Lösungen: Zu 1.: Erweitere die Menge der Ausdrücke, so dass Ausdrücke Funktionen beschreiben können. Dann kann die obige Wertvereinbarung genutzt werden. Zu 2.: Benutze spezielle Syntax für rekursive Funktionsdeklarationen. Genaueres dazu in Unterabschnitt 3.1.2. Beispiele: (Funktionsvereinbarungen) val string2charl:string->char list = explode; val charl2string = implode ; fun fac(n:int):int = if n=0 then 1 else n*fac(n-1) ; fun fac(n)= if n=0 then 1 else n*fac(n-1) ; 104

Typvereinbarungen: Zwei Probleme: 1. bisher keine Ausdrücke, die Typen als Ergebnis liefern 2. Typen können rekursiv sein, d.h. der vereinbarte Typbezeichner kommt im definierenden Typausdruck vor. Lösungen: Zu 1.: Führe Ausdrücke für Typen ein. Zu 2.: Benutze spezielle Syntax für rekursive Typdeklarationen. Genaueres dazu in Unterabschnitt 3.1.4. Beispiele: (Typvereinbarungen) type intpaar = int * int ; type charlist = char list ; type telefonbuch = (( string * string * string * int ) * string list ) list ; type intnachint = int -> int ; val fakultaet : intnachint = fac ; 105

Begriffsklärung: (Bezeichnerumgebung) Eine Bezeichnerumgebung ist eine Abbildung von Bezeichnern auf Werte (einschl. Funktionen) und Typen, ggf. auch auf andersartige Programmelemente. Oft spricht man auch von Namensumgebung oder einfach von Umgebung (engl. Environment). Beispiel: (Bezeichnerumgebung) Die elementaren Datenstrukturen von ML bilden die Standardumgebung für ML-Programme. Bemerkung: Eine Bezeichnerumgebung wird häufig als Liste von Bindungen modelliert. Jede Datenstruktur definiert eine Bezeichnerumgebung. 106

Begriffsklärung: (funktionales Programm) Ein funktionales Programm definiert eine Bezeichnerumgebung für die Anwender des Programms. Interaktive Laufzeitumgebungen für funktionale Programme erlauben das schrittweise Anreichern und Nutzen von Bezeichnerumgebungen. Beispiel: (funktionales Programm) val a = 7; val b = 8; fun f (x: int):int = x+a; val a = 20; val b = true; val d = f(a); Frage: Welchen Wert bezeichnet d? Bemerkung: Am obigen Beispiel kann man den Unterschied zwischen Wertvereinbarung und Zuweisung an Programmvariable erkennen. Im Folgenden wird er noch deutlicher werden. 107

3.1.2 Rekursive Funktionen Eine Funktion kann man durch einen Ausdruck beschreiben, in dem die Argumente der Funktion durch Bezeichner vertreten sind. Um deutlich zu machen, welche Bezeichner für Argumente stehen, werden diese deklariert. Alle anderen Bezeichner des Ausdrucks müssen anderweitig gebunden werden. Diesen Schritt von einem Ausdruck zu der Beschreibung einer Funktion nennt man Funktionsabstraktion oder λ-abstraktion. Beispiele: (Funktionsabstraktion) 1. Quadratfunktion: Ausdruck: x * x Abstraktion: λ x. x * x ML-Notation: fn x => x * x fn x : real => x * x Vereinbarung eines Bezeichners für die Funktion: val sq = fn x :real => x * x ; 108

2. Volumenberechnung eines Kegelstumpfes: Formel: Sei h die Höhe, r,r die Radien; dann ergibt sich das Volumen V zu π*h 2 2 V = * ( r + r * R + R ) 3 ML-Ausdruck: (Math.pi *h) / 3.0 * ( sq(r) + r * R + sq( R ) ) Abstraktion: λ h, r, R. (Math.pi *h)/3 * ( sq(r) + r * R + sq( R ) ) ML-Notation: fn ( h:real, r:real, R:real ) => (Math.pi * h)/3.0 * (sq(r) + r*r + sq(r)) Vereinbarung eines Bezeichners für die Funktion: val volkegstu = fn ( h, r, R ) => (Math.pi * h)/3.0 * (sq(r) + r*r + sq(r)) ; 109

3. Abstraktion über Funktionsbezeichner: Ausdruck: f ( f (x) ) Abstraktion: λ f, x. f ( f (x) ) ML-Notation: fn (f: real -> real, x) => f(f(x)) Vereinbarung eines Bezeichners für die Funktion: val twice = fn (f:real->real,x) => f(f(x)) val erg = twice ( sq, 3.0 ) ; 4. Abstraktion mit Funktion als Ergebnis: Ausdruck: f ( f (x) ) 1. Abstraktion: λ x. f ( f (x) ) 2. Abstraktion: λ f. ( λ x. f ( f (x) ) ) ML-Notation: fn f =>(fn x:real => f(f(x))) Vereinbarung eines Bezeichners für die Funktion: val appl2 = fn f =>(fn x:real => f(f(x))) ; val pow4 = appl2 ( sq ) ; val erg = pow4 ( 3.0 ) ; 110

Funktionsdeklaration In ML können Funktionen entweder mittels einer normalen Wertvereinbarung (siehe oben) oder mit einer speziellen Syntax deklariert werden. Erster Schritt zur Einführung der speziellen Syntax: fun <Bezeichner> <Signatur> = <Ausdruck> and <Bezeichner> <Signatur> = <Ausdruck>... and <Bezeichner> <Signatur> = <Ausdruck> ; wobei die Liste der mit dem Schlüsselwort and begonnenen Zeilen leer sein kann. Zunächst betrachten wir Signaturen der Form: <Signatur> ( <Params> ) <TypOpt> <Params> ε <ParamListe> <ParamListe> <ParamDekl> <TypOpt> ε <ParamDekl> <ParamDekl>, <ParamListe> <Bezeichner> <TypOpt> : <TypAusdruck> 111

Beispiel: (rekursive Funktionsdekl.) 1. Einzelne rekursive Funktionsdeklaration: fun rpow ( r: real, n: int ): real = if n = 0 then 1.0 else r * rpow (r,n-1) ; 2. Verschränkt rekursive Funktionsdeklaraion: fun gerade ( n: int ) : bool = n = 0 orelse ungerade (n-1) and ungerade ( n: int ) : bool = if n=0 then false else gerade (n-1) Deklaration rekursiver Funktionen Begriffsklärung: (rekursive Definition) Eine Definition oder Deklaration nennt man rekursiv, wenn der definierte Begriff bzw. das deklarierte Programmelement im definierenden Teil verwendet wird. 112

Bemerkung: Rekursive Definitionen finden sich in vielen Bereichen der Informatik und Mathematik, aber auch in anderen Wissenschaften und der nichtwissenschaftlichen Sprachwelt. Wir werden hauptsächlich rekursive Funktionsund Datentypdeklarationen betrachten. Definition: (rekursive Funktionsdekl.) Eine Funktionsdeklaration heißt direkt rekursiv, wenn der definierende Ausdruck eine Anwendung der definierten Funktion enthält. Eine Menge von Funktionsdeklarationen heißt verschränkt rekursiv oder indirekt rekursiv (engl. mutually recursive), wenn die Deklarationen gegenseitig voneinander abhängen. Eine Funktionsdeklaration heißt rekursiv, wenn sie direkt rekursiv ist oder Element einer Menge verschränkt rekursiver Funktionsdeklarationen ist. Begriffsklärung: (rekursive Funktion) Eine Funktion heißt rekursiv, wenn es rekursive Funktionsdeklarationen gibt, mit denen sie definiert werden kann. 113

Bemerkung: Die Menge der rekursiven Funktionen ist berechnungsvollständig. Rekursive Funktionsdeklarationen können als eine Gleichung mit einer Variablen verstanden werden, wobei die Variable von einem Funktionstyp ist. Beispiel: (Definition der Fakultätsfunktion) Gesucht ist die Funktion f, die folgende Gleichung für alle n Nat erfüllt: f(n) = if n=0 then 1 else n * f(n-1) Zur Auswertung von Funktionsanwendungen: Sei fun f(x) = A[x] In ML werden Funktionsanwendungen f(e) nach der Strategie call-by-value ausgewertet: Werte Ausdruck e aus; Ergebnis nennt man den akutellen Parameter z. Ersetze x in A[x] durch z. Werte den resultierenden Ausdruck A[z] aus. 114

Formen rekursiver Funktionsdeklarationen: Vereinfachend betrachten wir nur Funktionsdeklarationen, bei denen die Fallunterscheidung außen und die rekursiven Aufrufe in den Zweigen der Fallunterscheidung stehen. Eine rekursive Funktionsdeklaration heißt linear rekursiv, wenn in jedem Zweig der Fallunterscheidung höchstens eine rekursive Anwendung erfolgt (Beispiel: Definition von fac). Eine rekursive Funktionsdeklaration heißt repetitiv, wenn sie linear rekursiv ist und die rekursiven Anwendungen in den Zweigen der Fallunterscheidung an äußerster Stelle stehen. Beispiele: 1. Die übliche Definition von fac ist nicht repetitiv, da im Zweig der rekursiven Anwendung die Multiplikation an äußerster Stelle steht. 2. Die folgende Funktion facrep ist repetitiv: fun facrep ( n: int, res: int ): int = if n=0 then res else facrep( n-1, res*n ) 115

Eine rekursive Funktionsdeklaration für f heißt geschachtelt rekursiv, wenn sie Teilausdrücke der Form f (... f (... )... ) enthält. Eine rekursive Funktionsdeklaration für f heißt kaskadenartig rekursiv, wenn sie Teilausdrücke der Form h (... f (... )... f (... )... ) enthält. Beispiel: (kaskadenartige Rekursion) Berechne: Wie viele Kaninchen-Pärchen gibt es nach n Jahren, wenn man am Anfang mit einem Pärchen beginnt, jedes Pärchen nach zwei Jahren und dann jedes folgende Jahr ein weiteres Pärchen Nachwuchs erzeugt und die Kaninchen nie sterben. Die Anzahl der Pärchen stellen wir als Funktion fib von n dar: vor dem 1. Jahr: fib(0) = 1 nach dem 1. Jahr: fib(1) = 1 nach dem 2. Jahr: fib(2) = 2 116

nach dem n. Jahr: die im Jahr vorher existierenden Pärchen plus die Anzahl der neu geborenen und die ist gleich der Anzahl von vor zwei Jahren, also gilt: fib( n ) = fib( n-1 ) + fib( n-2 ) für n > 1. Insgesamt ergibt sich folgende kaskadenartige Funktionsdeklaration: fun fib ( n: int ):int = if n <= 1 then 1 else fib (n-1) + fib (n-2) Bemerkung: Aus Beschreibungssicht spielt die Form der Rekursion keine Rolle; wichtig ist eine möglichst am Problem orientierte Beschreibung. Aus Programmierungssicht spielt Auswertungseffizienz eine wichtige Rolle, und diese hängt von der Form der Rekursion ab. Beispiel: Kaskadenartige Rekursion führt im Allg. zu einer exponentiellen Anzahl von Funktionsanwendungen. ( z.b. bei fib(30) bereits 1.664.079 Anwendungen ) 117

3.1.3 Listen und Tupel Die Datenstruktur der Listen Eine Liste über einem Typ T ist eine total geordnete Multimenge mit endlich vielen Elementen aus T (bzw. eine endliche Folge, d.h. eine Abb. {1,...,n } T ). ML stellt standardmäßig eine Datenstruktur für Listen bereit, die bzgl. des Elementtyps parametrisiert ist: Typen: Ist t ein ML-Typ, dann bezeichnet t list den Typ der Listen mit Elementen aus t. Funktionen: hd tl : t list t : t list t list _::_ : t * t list t list (* cons *) null : t list bool (* Test, ob Liste leer *) length: t list int _@_ rev Konstanten: nil : t list * t list t list : t list t list : t list (* Revertieren/Umkehren einer Liste *) 118

Dem Typ t list ist als Wertemenge die Menge aller Listen über Elementen vom Typ t zugeordnet. Bemerkungen: In ML gibt es eine spezielle Notation für Listen: statt a :: a ::... :: a :: nil k k-1 1 kann man [ a,a,...,a ] k k-1 1 schreiben; insbesondere [ ] für nil. Selbstverständlich kann man mit Listen von Listen arbeiten. Wie wir in 3.3 sehen werden, unterstützt ML Typparameter. Z. B. ist nil vom Typ a list, wobei a eine Typvariable ist. Beispiele: (Funktionen auf Listen) 1. Addiere alle Zahlen einer Liste von Typ int list: - fun mapplus ( xl: int list ) = if null(xl) then 0 else hd(xl) + mapplus(tl(xl)) ; - mapplus ( [ 1,2,3,4,5,6 ] ) ; 119

2. Prüfen einer Liste von Zahlen auf Sortiertheit: - fun ist_sortiert ( xl ) = if null (xl) orelse null ( tl(xl) ) then true else if hd (xl) <= hd(tl(xl)) then ist_sortiert( tl (xl)) else false ; 3. Zusammenhängen zweier Listen (engl. append): - fun append ( l1, l2 ) = if l1 = [ ] then l2 else hd (l1) :: append ( tl(l1), l2 ) ; 4. Umkehren einer Liste: - fun rev ( xl ) = if xl = nil then [ ] else append ( rev (tl (xl)), [ hd(xl) ] ) ; 5. Zusammenhängen der Elemente einer Liste von Listen: - fun concat ( xl ) = if null(xl) then [ ] else append ( hd(xl), concat( tl( xl ) ) ) ; 120

6. Wende eine Liste von Funktionen vom Typ int int nacheinander auf eine ganze Zahl an: - fun seqappl ( xl : (int int) list, i: int ) : if null( xl ) then i else seqappl ( tl(xl), (hd(xl) i)) ; Bemerkungen: Rekursive Funktionsdeklaration sind bei Listen angemessen, weil Listen rekursive Datenstrukturen sind. Mit Pattern Matching lassen sich die obigen Deklaration noch eleganter fassen (s. unten). Die Datenstrukturen der Tupel Wir betrachten zunächst Paare und verallgemeinern dann auf n-tupel: Paare oder 2-Tupel sind die Elemente des kartesischen Produktes zweier ggf. verschiedener Mengen oder Typen. Paare gehören zu Produkttypen. Als Typkonstruktor wird * in Infix-Schreibweise benutzt: 121

Typen: Sind t1, t2 ML-Typen, dann bezeichnet t1 * t2 den Typ der geordneten Paare aus Elementen von t1 und t2. Funktionen: (_,_): t1 * t2 t1 * t2 (* Paarbildung *) #1 : t1 * t2 t1 (* 1. Komponente *) #2 : t1 * t2 t2 (* 2. Komponente *) Konstanten: keine Dem Typ t1*t2 ist die Menge der Paare bestehend aus Elementen von t1 und t2 zugeordnet. Beispiel: (Funktionen auf Paaren) Transformiere eine Liste von Paaren in ein Paar von Listen: fun unzip ( xl:( a* b) list ): if null( xl ) then ([],[]) ( a list)*( b list) = else ( (#1(hd(xl))) :: (#1(unzip(tl(xl)))), (#2(hd(xl))) :: (#2(unzip(tl(xl)))) ); ( auch das geht erheblich schöner mit Pattern Matching ) 122

ML unterstützt n-tupel für alle n 0, n 1: Für n=0 ist es die triviale Datenstruktur Unit: Typ: unit Funktionen: keine Konstanten: (): unit Die Wertemenge zum Typ unit enthält genau ein Element, genannt Einheit (engl. unity). Bemerkung: unit wird oft als Ergebnistyp verwendet, wenn es keine relevanten Ergebniswerte gibt. Für n=1 gilt: 1-Tupel vom Typ t werden wie Elemente vom Typ t betrachtet. Für n>1 gilt: n-tupel sind eine Verallgemeinerung von Paaren. Klammern werden zur Tupel-Konstruktion verwendet. #i mit 0<i n selektiert die i-te Komponente. 123

Beispiel: (Geschachtelte Tupel) Mit der Tupelbildung lassen sich baumstrukturierte Werte, sogenannte Tupelterme, aufbauen. So entspricht der Tupelterm: ((8,true), (("Tupel","sind","toll"),"aha")); dem Baum: 8 true "aha" "Tupel" "sind" "toll" Beispiel: (Funktionen auf n-tupeln) 1. Flache ein Paar von Tripeln in ein 6-Tupel aus: - fun ausflachen ( p: ( a * b * c) * ( d * e * f) ) = ( #1(#1(p)), #2(#1(p)), #3(#1(p)), #1(#2(p)), #2(#2(p)), #3(#2(p)) ) ; 2. Funktion zur Paarbildung: - fun paarung (lk,rk) = (lk,rk) ; 124

Stelligkeit von Funktionen: Formal gesehen, sind alle Funktionen in ML einstellig, haben also nur ein Argument. Die Klammern um das Argument kann man weglassen, also f a statt f(a). Statt mehrerer Parameter übergibt man ein Tupel von Parametern. Die Tupelklammern kann man natürlich nicht weglassen. Beispiel: Die Additionsoperation in ML op + nimmt nicht zwei Argumente sondern ein Tupel von Argumenten. Statt mehrere Parameter als Tupel zu übergeben, kann man sie auch nacheinander übergeben. Z. B. hat die Funktion fun curriedplus m n = m + n ; den Typ fn : int (int int) Die Auswertung des Ausdruck curriedplus 7 liefert also eine Funktion vom Typ (int int). 125

Zusammenfassung: In ML haben alle Funktionen nur eine Parameter! Funktionen mit n-parametern werden in ML durch einstellige Funktionen ausgedrückt, die ein n-tupel als Parameter nehmen! Die bisher verwendeten Klammern bei einstelligen Funktionen sind also überflüssig. Syntax der Funktionsanwendung in ML: <Ausdruck> <Ausdruck> Funktionstyp zum Funktionstyp passender Parametertyp Pattern Matching Pattern Matching meint in diesem Zusammenhang die Verwendung von Mustern zur Vereinfachung von Vereinbarungen/Bezeichnerbindungen. 126

Beispiel: (Funktionssyntax) - fun ist_sortiert x = if null x orelse null (tl x) then true else if hd x <= hd (tl x) then ist_sortiert( tl x ) - fun append (l1,l2) = if l1 = [ ] then l2 - fun concat x = if null x then [ ] else false ; else hd l1 :: append ( tl l1, l2 ) ; else append ( hd x, concat ( tl x ) ) ; - fun unzip ( x : ( a * b) list ): ( a list) * ( b list) = if null x then ([],[]) else ( #1 (hd x) :: (#1 (unzip (tl x))), #2 (hd x) :: (#2 (unzip (tl x))) ) ; 127

Begriffsklärung: (Konstruktoren) Konstruktoren in ML sind bestimmte Funktionen: - die Funktionen zur Tupelbildung - die Listenfunktion _::_ (daher der Name cons) - benutzerdefinierte Konstruktoren zu rekursiven Datentypen (siehe 3.1.4). Begriffsklärung: (ML-Muster, ML-Pattern) Muster (engl. Pattern) in ML sind Ausdrücke gebildet über Bezeichnern, Konstanten und Konstruktoren. Alle Bezeichner in einem Muster müssen verschieden sein. Ein Muster M mit Bezeichnern b,..., b passt auf 1 k einen Wert w (engl. a pattern matches a value w), wenn es eine Substitution der Bezeichner b in M i durch Werte v gibt, in Zeichen M[v /b,..., v /b ], i 1 1 k k so dass M[v /b,..., v /b ] = w 1 1 k k 128

Beispiel: (ML-Muster, Passen) 1. (a,b) passt auf (4,5) mit Substitution a=4, b=5. 2. (a,b) passt auf (~47, (true, dada )) mit a = ~47, b = (true, dada ). 3. x::xs passt auf 7::8::9::nil mit x = 7 und xs = 8::9::nil, d.h. xs = [ 8, 9 ]. 4. x1::x2::xs passt auf 7::8::9::nil mit x1 = 7, x2 = 8, x3 = [ 9 ]. 5. first :: rest passt auf [ computo, ergo, sum ] mit first = computo, rest = [ ergo, sum ] 6. ( (8,x), (y,"aha") ) pass auf ((8,true), ( ("Tupel","sind","toll"), "aha") ) x = true und y = ("Tupel","sind","toll") mit 8 x y "aha" 129

Muster können in ML-Wertvereinbarungen verwendet werden: val <Muster> = <Ausdruck> ; Wenn das Muster auf den Wert des Ausdrucks passt und σ die zugehörige Substitution ist, werden die Bezeichner im Muster gemäß σ an die zugehörige Werte gebunden. Beispiel: (Wertvereinbarung mit Mustern) - val ( a, b ) = ( 4, 5 ); val a = 4 : int val b = 5 : int -a+b; val it = 9: int Muster können in ML-Funktionsdeklarationen verwendet werden: fun <Bezeichner> <Muster> = <Ausdruck>... <Bezeichner> <Muster> = <Ausdruck> ; 130

Beispiel: (Funktionsdeklaration mit Mustern) 1. Deklaration von mapplus ohne und mit Mustern: - fun mapplus (xl: int list) = if null xl then 0 else (hd xl)+ mapplus (tl xl) ; - fun mapplus [] = 0 mapplus (x::xl) = x + mapplus xl ; 2. Einige der obigen Funktionsdeklaration mit Verwendung von Mustern: - fun ist_sortiert [ ] = true ist_sortiert (x::nil) = true ist_sortiert (x1::x2::xs) = if x1 > x2 then false else ist_sortiert (x2::xs ) ; - fun append ([],l2) = l2 append (x::xs,l2) = x::append (xs,l2) ; 131

3. Verwendung geschachtelter Muster: - fun unzip (xl:( a* b)list): ( a list)*( blist) = if null xl then ([],[]) else ((#1 (hd xl))::(#1 (unzip (tl xl))), (#2 (hd xl))::(#2 (unzip (tl xl)))); - fun unzip [] = ([],[]) unzip ((x,y)::ps) = (x::(#1 (unzip ps)), y::(#2 (unzip ps)) ); let- und case-ausdrücke: Der Mustermechanismus kann auch innerhalb von Ausdrücken eingesetzt werden. 1. Syntax des let-ausdrucks: let <Liste von Deklarationen> in <Ausdruck> end Die aus den Deklarationen resultierenden Bindungen sind nur im let-ausdruck gültig. D.h. sie sind sichtbar im let-ausdruck, an den Stellen, an denen sie nicht verdeckt sind. 132

2. Syntax des case-ausdrucks: case <Ausdruck0> of <Muster1> => <Ausdruck1>... <MusterN> => <AusdruckN> Bedeutung: Werte Ausdruck0 aus und prüfe der Reihe nach, ob der resultierende Wert auf eines der Muster passt. Passt er auf ein Muster, nehme die entsprechenden Bindungen vor und werte den zugehörigen Ausdruck aus (die Bindungen sind nur in dem zugehörigen Ausdruck gültig). Beispiele: (let-ausdruck) - val a = let val b = 7*8 in b*b end; - val a = let val a = 7*8 in let val (a,b) = (a,a+1) in a*b end end; - fun unzip [] = ([],[]) unzip ((x,y)::ps) = let (xs,ys) = unzip ps in (x::xs,y::ys) end ; 133

Beispiel: (case-ausdruck) - fun ist_sortiert xl = case xl of [] => true (x::nil) => true (x1::x2::xs) => if x1 > x2 then false else ist_sortiert (x2::xs) ; Bemerkungen: Das Verbot von gleichen Bezeichnern in Mustern hat im Wesentlichen den Grund, dass nicht für alle Werte/Typen die Gleichheitsoperation definiert ist. - val mal2 = fn x => 2 * x ; - val twotimes = fn x => x+x ; - val (a,a) = ( mal2, twotimes ) Offene Frage: Was passiert, wenn keines der angegebenen Muster passt? Abrupte Terminierung. 134

3.1.4 Benutzerdefinierte Datentypen Übersicht: Vereinbarung von Typbezeichnern Deklaration neuer Typen Summentypen Rekursive Datentypen Fast alle modernen Spezifikations- und Programmiersprachen gestatten es dem Benutzer, neue Typen zu definieren. Vereinbarung von Typbezeichnern ML erlaubt es, Bezeichner für Typen zu deklarieren (vgl. Folie 105). Dabei wird kein neuer Typ definiert, sondern nur ein Typ an einen Bezeichner gebunden. 135