Grundlegende Begriffe: Funktionen

Werbung
2
Grundlegende Begriffe:
Funktionen
Hohe Gedanken brauchen eine hohe Sprache
Aristophanes
In diesem Kapitel werden wichtige Eigenschaften von Funktionen besprochen. Dazu werden zunächst die grundlegenden mathematischen Begriffe
zusammengefaßt. In der funktionalen Programmierung sind definierende
Gleichungen von Funktionen das A und O. Definitionen sind funktionale
Programme.
Abstraktion ist ein fundamentales Prinzip der Informatik. Eine Funktion
kann als Abstraktion eines Ausdrucks aufgefaßt werden. Wie das technisch
aussieht, wird ebenfalls in diesem Kapitel diskutiert.
Die Lernziele im Einzelnen:
• Den mathematischen Funktionsbegriff verstehen
• Unterschied zwischen partiellen und totalen Funktionen
• Definierende Gleichungen
• Funktionen als Abstraktion von Ausdrücken
• Funktionen als Argumente von Funktionen
• Klammerfreie Schreibweise von Funktionen nach Curry
• Fallunterscheidung zur Funktionsdefinition
Vers. 7.11.98
6
Funktionen
2.1 Funktionen
Funktionen sind aus der Mathematik bekannt. Mit Funktionen kann man nicht
nur rechnen sondern auch Algorithmen formulieren. Davon handelt diese Vorlesung. Die Begriffsbildungen in Mathematik und Informatik sind völlig gleichartig. Allerdings interessiert man sich im allgemeinen in der Mathematik für die
Eigenschaften einer Funktion (z.B. Stetigkeit, Differenzierbarkeit), in der Informatik dagegen konkret für die Berechnung von Funktionswerten.
Eine der einfachsten denkbaren Funktionen ist die Identität:
> id’ x = x
?: id’ hello world
„hello world“
Diese Funktion1 liefert ihr Argument x als Funktionswert. Etwas anspruchvoller
ist die Funktion:
> quadrat x = x*x
?: quadrat 4
16
Die Definition von quadrat stützt sich auf die primitive Funktion * ab2.
Konstanten sind 0-stellige Funktionen:
> seite = 5
> flaeche = quadrat seite
?: flaeche
25
Wir klären zunächst einige Begriffe:
DEF.:
Eine Funktion f ist eine rechtseindeutige Relation f ⊆ A × W über der
Argumentmenge A und der Wertemenge W,
d.h: (a,w), (a,w’) ∈ f ⇒ w = w’.
1. Folgende Konventionen gelten in diesem Text:
> steht am Beginn einer Zeile, die eine Definitionsgleichung oder Teil davon enthält.
?: steht am Beginn einer Zeile, die Eingabe für das Haskell-System ist. In der / den Folgezeile(n) steht das Ergebniss.
2. Operatoren wie +, * usw. können als Funktionen aufgefaßt werden. Dazu später mehr.
Vers. 0.2 / 10-98
7
Grundlegende Begriffe: Funktionen
Für (a,w) ∈ f schreibt man: f(a) = w oder (f a) = w oder f a == w . In Definitionen und allgemeinen Erörterungen verwenden wir die mathematische
Schreibweise f(a) , in HASKELL-Programmen die klammerfreie Schreibweise f a.
Die Vorteile werden später klar, anfangs ist sie etwas gewöhnungsbedürftig.
Statt Argument- bzw. Wertemenge sagt man oft Argumentbereich und Wertebereich.
ABBILDUNG 1.
Funktion dargestellt als Relation
Einem Argument
werden nie
zwei verschiedene
Werte zugeordnet
A
W
Die in Abb. 1. dargestellte Funktion f ist so zu verstehen: Die Pfeile verlaufen
vom Argumentbereich zum Wertebereich. Ein Argument x besitzt den Wert w ,
auf den der Pfeil zeigt, der bei x beginnt. f besitzt einige typische Eigenschaften:
• Die Elemente des Argumentbereichs sind gleichartig. Man sagt: sie haben
den gleichen Typ .
• Gleiches gilt für den Wertebereich, dessen Typ sich von dem des Argumentbereichs — wie im Beispiel — unterscheiden kann, nicht muß.
• Die Anzahl der Elemente von Argument- und Wertbereich ist unterschiedlich. Auch das kann, muß nicht so sein. A oder W können auch unendlich
sein.
• Es gibt einen Wert v, der mehrere Urbilder a, a’ hat, d.h.
f(a) = f(a’) = v . Deshalb ist die dargestellte Funktion nicht injektiv .
• Nicht alle Elemente des Wertebereichs treten als Wert eines Arguments auf.
Die Funktion ist nicht surjektiv.
8
Funktionen
• Nicht jedes Argument besitzt einen Funktionswert. Die Funktion ist partiell .
Die Menge der Argumente, auf der eine Funktion definiert ist, heißt Definitionsbereich von f .
Partielle Funktionen
sind gefährlich!
Partielle Funktionen machen dem Programmierer das Leben schwer: vor einer
Funktionsapplikation f(a) muß nämlich geprüft werden, ob f für a überhaupt
definiert ist. Sonst kann die Ausführung des Programms zur Katastrophe führen.
Das beliebteste Beispiel ist die Division durch 0. Die (ganzahlige) Division kann
man offenbar als Funktion div : Int × Int→ Int auffassen. div ist eine zweistellige Funktion. Der Argumentbereich besteht aus Paaren von ganzen Zahlen
— Divident und Divisor —, der Wertebereich ebenfalls aus ganzen Zahlen,
deren Typ hier mit Int bezeichnet wird. Ob div (4711 , (a - b) ) wohldefiniert ist,
hängt davon ab, ob a ≠ b . Die Division ist nämlich partiell. Bekanntlich ist sie
auf der Menge {(x,0) | x aus Int } undefiniert.
Die einführten Begriffe werden zusammengefaßt in:
DEF.:
Eine Funktion f : A → W heißt
•injektiv, wenn a,a’ ∈ A : a ≠ a’ ⇒ f(a) ≠ f(a’)
•surjektiv, wenn ∀ w ∈ W ∃ a ∈ A : f(a) = w
•bijektiv, wenn f injektiv und surjektiv ist
•total, wenn ∀ a ∈ A : ∃ w ∈ W : f(a) = w
•partiell, wenn sie nicht total ist.
•Der Definitionsbereich von f ist die Teilmenge A’ ⊆ A , auf deren Elementen f definiert ist. Wenn A’ ⊂ A , dann ist f partiell.
•Eine Funktionen von n Elementen f : A → W,
A = A1 ×… × An , heißt n-stellig.
Unter der Komposition von Funktionen
f : A → W und g : W→ V
versteht man die Funktion
h = g . f , h : A → V mit
h(a) = g( f(a) ).
Injektive Funktionen f besitzen eine Umkehrfunktion f -1. Für alle Argumente x,
für die f definiert ist, gilt:
f -1 ( f(x)) = x oder f -1. f = id
Vers. 0.2 / 10-98
9
Grundlegende Begriffe: Funktionen
2.2 Funktionsdefinition,Variablen, Parameter
Funktionen werden durch Gleichungen definiert. Diese bilden die Grundlage der
Berechnung von Funktionswerten. Kommt auf der rechten Seite der definierenden Gleichung einer Funktion f eine Funktion g vor, sagen wir: f stützt sich auf
g ab, z.B f(x) = g(x) + k .
Um den Wert f(a) zu berechnen, muß der Wert g(a) berechnet und um k
erhöht werden. Funktionen, die im Rahmen der Programmiersprache nicht definiert zu werden brauchen, heißen primitive Funktionen. Dazu gehören insbesondere die arithmetischen Funktionen. Primitive Funktionen sind im allgemeinen
keine Bibliotheksfunktionen. Jedes Programmiersystem kennt Programmbibliotheken, in funktionalen Programmiersprachen auch Funktionsbibliotheken
genannt. Sie enthalten nützliche, häufig verwendete Programme (Funktionen), so
daß nicht jeder Programmierer das Rad neu erfinden muß.
Beispiele: quadrat stützt sich auf die primitive Funktion (hier besser Operation) * ab. Die Funktion
> quaderVol h = flaeche * h
Definierende
Gleichungen sind
das A und O der
funktionalen
Programmierung
stützt sich auf die oben definierte Funktion flaeche und die primitive Funktion
* ab. Sie berechnet das Volumen eines Quaders der Grundfläche flaeche mit
der Kantenlänge seite und der Höhe h. Die nicht sonderlich interessante
Funktion:
> id’’ x = sqrt (quadrat x)
stützt sich auf die Bibliotheksfunktion sqrt sowie auf quadrat ab.
Die Klammerung von quadrat x ist wichtig, sonst würde sqrt auf die Funktion quadrat angewendet und nicht auf den Wert, der sich bei Berechnung von
quadrat x ergibt.
Hinter dieser Feststellung stecken mehrere wichtige Fragen: Wie wird ein Funktionswert eigentlich berechnet? In welcher Reihenfolge werden Teile einer Definition berechnet? Wie wird der Versuch der Berechnung von Funktionswerten
für nicht erlaubte Argumente von z.B. von quadrat ’’Hello World’’ verhindert? Dazu später.
Funktionsdefinitionen sind strikt von der Berechnung von Funktionswerten zu
unterscheiden. Ist f eine bereits definierte Funktion und a ein Wert aus dem
Definitionsbereich der Funktion, so ist f a ein Funktionswert aus dem Wertebereich von f . Werte werden berechnet, wenn f aufgerufen wird, sei es interaktiv
10
Funktionsdefinition,Variablen, Parameter
vom Benutzer, sei es im Zuge der Berechnung einer Funktion h, auf die sich die
Definition von h abstützt. f a nennt man auch eine Funktionsapplikation.
Wir wollen einen ersten Eindruck davon vermitteln, wie die Berechnung von
Funktionswerten stattfindet. Der Ausdruck
Berechnung von
Ausdrücken
erfolgt durch
Reducktion
quaderVol 10
soll berechnet werden. Dazu wird die Definition von quaderVol eingesetzt.
Alle Vorkommen des formalen Parameters h müssen durch den aktuellen Parameter 10 ersetzt werden. Wir erhalten
flaeche * 10
Im nächsten Schritt wird flaeche durch seine Definition (s.o.:
flaeche = quadrat seite) ersetzt. Das Ergebnis ist:
quadrat seite 10
Unter Verwendung der Definitionen von seite bzw quadrat erhält man in den
folgenden Schritten:
seite * seite * 10
seite * 5 * 10
seite * 50
5 * 50
250
Einen Ersetzungsschritt nennt man Reduktion. Beachte, daß die Folge der
Reduktionsschritte nichtdeterministisch sein kann. Im Beispiel hätte z.B. seite
vor quadrat reduziert werden können. Ein reduzierbarer (Teil-)Ausdruck heißt
Redex (reducible expression). Das Verfahren ist nichtdeterministisch, wenn
irgendwann im Verlauf der Reduktion mehr als eine Redex im zu reduzierenden
Ausdruck vorkommt. Das Reduktions- oder Berechnungsverfahren endet, wenn
es keinen Redex mehr gibt. Entweder ist dann bereits der Funktionswert berechnet oder der Ausdruck enthält nur noch primitive Funktionen, die systemintern
ausgewertet werden. Dazu gehört die Multiplikation * . Ein nicht mehr reduzierbarer Ausdruck wird Normalform genannt.
Es schließen sich mehrere Fragen an: terminiert die Folge von Reduktionen
immer? Angenommen, man könnte quadrat x durch x*x und umgekehrt ersetzen. Dann gäbe es offensichtlich eine nichtterminierende Folge von Reduktionen. Liefern verschiedene Reduktionsfolgen dieselbe Normalform? Sind
Vers. 0.2 / 10-98
11
Grundlegende Begriffe: Funktionen
primitive Funktionen unverzichtbar oder dienen sie nur einer effizienteren Ausführung? Wir werden den Fragen später nachgehen.
In Definitionen:
formale Parameter
In Ausdrücken:
Argumente oder
aktuelle Parameter
Variablen sind in
funktionalen
Sprachen nicht
veränderlich!
Folgende Sprechweise hat sich in der Informatik eingebürgert: die im Zuge der
Definition einer Funktion verwendeten Variablen werden oft (formale) Parameter genannt, die Werte, für die ein Funktionswert berechnet werden soll, heißen
Argumente, oder aktuelle Parameter.
Die Bezeichung Variable ist eigentlich unpassend. Sie entstammt der Mathematik, wo man gelegentlich von einer unabhängigen Veränderlichen spricht, womit
nichts anderes gemeint ist, als daß für eine Variable x ein beliebiger Wert des
Definitionsbereichs eingesetzt werden kann.
In einem funktionalen Programm ist eine Variable ein Bezeichner für einen Wert.
Im Gegensatz zu imperativen Programmiersprachen1 wie Pascal, Java oder C ist
der Wert, den eine Variable bezeichnet, nicht veränderbar. Als wichtige Konsequenz halten wir fest, daß Funktionen für gleiche Argumentwerte immer das
gleiche Ergebnis liefert — eine in der Mathematik selbstverständliche Eigenschaft, die in imperativen Programmiersprachen nicht erfüllt ist.
2.3 Funktionen als Abstraktion von Ausdrücken
Funktionsapplikationen gehören zur den Ausdrücken oder Formeln (expressions)
der Sprache. Sie besitzen einen Wert, z.B.
?: sqrt (quadrat 5 + quadrat 7)
8.60233
Mit arithmetischen Ausdrücken verhält es sich selbstverständlich genau so:
?: 3+5*(3-7)
-17
ist kein besonders aufregendes Exemplar. Wie die Menge aller Ausdrücke genau
definiert ist — ihre Syntax von Ausdrücken — wird in der Programmiersprache
festgelegt; dazu später mehr.
Wir wollen vielmehr der Frage nachgehen, wie sich Funktionsdefinitionen und
Ausdrücke zueinander verhalten. Ausdrücke werden, wie eben festgestellt, im
1. Später werden die Untersschiede von funktionalen und imperativen Programmiersprachen genauer herausgearbeitet.
12
Funktionen als Abstraktion von Ausdrücken
wesentlichen durch Funktionsapplikation gebildet. Umgekehrt kann eine Funktion auch als Abstraktion eines Ausdrucks aufgefaßt werden kann:
Abstraktion von
Konstanten in
Ausdrücken
liefert Funktionen
> abstr3 x = x+5*(x-7)
ist eine Funktion, die von der Konstanten 3 abstrahiert. Manchmal nennt man das
auch Parametrisierung des Ausdrucks. Nicht überraschend, daß
?: abstr3 3
-17
den gleichen Wert liefert wie der ursprüngliche Ausdruck, in dem der hier als
Argument verwendete Wert 3 ,,fest verdrahtet”1 ist. Durch Abstraktion sind aus
einem Ausdruck unendlich viele entstanden, u.a.
?: abstr3 4711
28231
was dem Ausdruck 4711+5*(4711-7) entspricht.
Die Abstraktion von Ausdrücken zu Funktionen ist so wichtig, daß HASKELL dafür
eine spezielle Notation bereitstellt, die sogenannte λ−Abstraktion. Sie besteht
aus einem Ausdruck, in dem sogenannte freie Variablen vorkommen und Variablenbindungen der Form λ x .
?: \x -> x+5*(x-7)
<<function>>
Auch anonyme
Funktionen
können
nützlich sein
Das liest sich so: \x -> x+5*(x-7) ist die Funktion von x, die als Wert die
Zahl liefert, die sich ergibt, wenn man für x eine Zahl a als Argument einsetzt
und a+5*(a-7)ausrechnet, z.B:
?: (\x -> x+5*(x-7)) 4711
28231
Das Ergebnis überrascht nicht. Der einzige Unterschied zu abstr3 besteht
darin, daß wir im zweiten Fall eine anonyme Funktion verwendet haben. Das ist
manchmal nicht nur praktisch, sondern hat einen tiefliegenden theoretischen
Hintergrund, auf den wir noch ausführlich zu sprechen kommen.
Abstraktion ist ein zentrales Prinzip der Informatik. Es geht im Kern darum,
durch Abschwächen von bestimmten Randbedingungen — im Beispiel das Vorkommen der Konstante 3 in einem auszuwertenden Ausdruck — ein allgemeineres Problem zu lösen, von dem das ursprüngliche ein Spezialfall ist.
1. Informatikjargon für alles, was nicht parametrisiert ist
Vers. 0.2 / 10-98
13
Grundlegende Begriffe: Funktionen
2.4 Funktionen von Funktionen: Curry-Darstellung
Von der ungeklammerten Schreibweise von Funktionsdefinitionen und -applikationen, der Curry-Notation (currying),1 haben wir schon Gebrauch gemacht.
Was hat es damit auf sich? Als erstes ist festzustellen, daß die Definition von
Funktionen (Def 1.1) nahezu beliebige Argument- und Wertebereiche zuläßt.
Argumente können Paare sein — wie im Fall der Division, auch Zeichenketten,
sogar Funktionen. Tatsächlich ist dies der Schlüssel zum Verständnis der CurryNotation von Funktionen. Wir betrachten eine Funktion max’2, die das Maximum von zwei Zahlen berechnet:
> max’ y x = if x > y then x else y
3
Die Funktion max0:
> max0 x = max’ 0 x
hat nur ein Argument. Sie entsteht aus max’ durch Fixieren des ersten Arguments von max’. Das läßt sich auch direkt machen:
?: max’ 0
<<function>>
(max’ 0) ist also eine Funktion, und zwar eine Funktion mit einem Parameter.
Sie bildet das Maximum von 0 und dem aktuellen Parameter:
?: (max’ 0) 23
23
D.h.: die Funktion verhält sich wie max0. Tatsächlich ist es die gleiche Funktion!
max’ angewandt auf sein erstes Argument y liefert eine Funktion, die angewandt auf das zweite Argument x das Maximum von x und y berechnet. Es
besteht aber kein Anlaß, der Funktion einen Namen zu geben. Das wäre lästig,
denn es gibt ziemlich viele davon: max0, max1,.., max4711,..,
maxMinus1,.., für jedes mögliche erste Argument eine. Es reicht also, (max’
x) wie eine anonyme Funktion zu behandeln, gleich wie das konkrete Argument
x aussieht.
Das mag etwas kompliziert erscheinen, hat aber den großen Vorteil der Einheit1. Benannt nach dem Logiker Haskell Curry, gleichzeitig Namenspatron für die Programmiersprache HASKELL. Die nach
Curry benannte Notation wurde zuerst von dem Logiker M. Schönfinkel vorgeschlagen.
2. Bezeichner dürfen das Zeichen ’ enthalten. Wir verwenden die "gestrichene" Schreibweise bessonders, um Namenskollisionen mit in der Standardbibliothek zu vermeiden. max ist so eine Bibliotheksfunktion. Der Versuch, eine eigene Funktion max zu definieren, führt zu einem Fehler.
3. Fallunterscheidungen if .. then .. else werden im nächsten Abschnitt behandelt.
14
Funktionen und Operatoren — Präzedenz und Assoziation
n-stellige Funktionen lassen
sich durch einstellige ausdrücken
lichkeit: es gibt intern nur Funktionen mit einem Argument. Abgesehen vom
praktischen Vorteil, den Einheitlichkeit immer mit sich bringt, gibt es dafür eine
wichtige theoretische Grundlage, die in Kapitel 5 behandelt wird.
Zur Klarstellung: Funktionsdefinitionen (des Programmierers) können selbstverständlich wie bisher ein oder mehrere Argumente haben, die klammerfrei
geschrieben werden. Daß es sich tatsächlich im wesentlichen um Funktionen
handelt, die Funktionen auf Funktionen abbilden -- sogenannte Funktionale -spielt nur hinter den Kulissen eine Rolle.
Wir fassen die Erörterung von Funktionen in Curry-Notation zusammen:
Es gilt: Zu jeder n-stelligen Funktion
f : A1 × … × An → W
gibt es eine Folge fc1, ... fcn von einstelligen Funktionen , so daß für alle
(a1,...,an), für die f(a1,...,an) definiert ist, gilt:
fc1(a1) = fc2, fc2(a2) = fc , ...., fcn-1(an-1) = fcn, fcn(an) = f(a1,...an) .
Da auch das Umgekehrte gilt, handelt es sich wirklich nur um zwei verschiedenen Darstellungsformen.
Das alles klingt komplizierter als es ist, da die Funktionen fcn , von denen eben
die Rede war, für den Programmierer (fast) gar nicht sichtbar sind. Allerdings
sind manche Fehlermeldungen des HUGS-Systems ohne ein Verständnis der
Curry-Notation nicht interpretierbar. Dazu in Kürze mehr.
2.5 Funktionen und Operatoren — Präzedenz und
Assoziation
Operatoren können
geklammert wie
Funktionen behandelt werden
(Abschnitt, section)
Operatoren sind Funktionen in anderer Schreibweise: Infix-Operatoren stehen im
allgemeinen zwischen zwei Argumenten — wie das von arithmetischen Ausdrücken bestens bekannt ist. Manchmal ist für Operatoren die Präfix-Schreibweise nützlich. In HUGS setzt man dazu einfach das Operatorsymbol in
Klammern. (+) ist die Funktion, die die Summe ihrer Argumente als Wert liefert. (+) kann jetzt überall stehen, wo ein Funktionssymbol erlaubt ist.
Umgekehrt kann man Operatoren wie Funktionen definieren. Das Operatorsymbol wird, wenn es an der Stelle einer Funktion steht, in Klammern gesetzt.
Vers. 0.2 / 10-98
15
Grundlegende Begriffe: Funktionen
Besonders in Verbindung mit der im vorigen Abschnitt besprochenen Möglichkeit, Funktionen mit mehreren Argumenten teilweise anzuwenden, erweist sich
die Präfixschreibweise als nützlich:
Ist ⊗ ein zweistelliger Operator und a, b gültige Argumente, so heißen (a ⊗ )
und (⊗ b) Abschnitte (sections), die durch
(a ⊗ ) y = a ⊗ y
(⊗ b) x = x ⊗ b
definiert sind.
Naheliegende Beispiele sind die Verdopplung (2*) oder der Test, ob ein Wert
positiv ist: (> 0)
‘ und ´
haben verschiedene
Bedeutung
?: (> 0) (-3 * -3)
True
Eine Ausnahme bildet (-x): ’-’ ist die einstellige numerische Negation, die den
negativen Wert ihres numerischen Arguments x liefert.
Funktionsbezeichner können ihrerseits so syntaktisch verändert werden, daß sie
wie Operatoren einsetzbar sind. Dazu muß der Bezeichner in rückwärts gerichtete einfache Anführungszeichen (backquotes) ‘ gesetzt werden. Operatorschreibweise und die Schreibweise für Funktionsapplikation können gemischt
verwendet werden.
?: max (7*2) ((+) 3 12)
15
Vorrangregeln
helfen Klammern sparen
Ein gutes Beipiel für dei Nützlichkeit der Schreibweise sind boolesche Funktionen: Definiert man eine Funktion implies für die aussagenlogische Implikation und sind a und b aussagenlogische Werte1, dann ist die erste der beiden
“aquivalenten Formulierungen offenbar angemessener:
?: a ‘implies‘ b
True
?: implies a b
True
Wie der Ausdruck a*a + 2*a*b + b*b zu berechnen ist, ist jedem geläufig,
wenn a und b wohldefiniert sind: ,,Punktrechnung geht vor Strichrechnung”.
Solche Vorrangregeln (Prioritäts-, Präzedenzregeln) von Operatoren helfen
Klammern sparen. Operatoren höherer Priorität werden vor denen niedrigerer
1. Wie man boolsche Werte ausdrückt, behandeln wir im nächsten Abschnitt.
16
Funktionen und Operatoren — Präzedenz und Assoziation
Priorität angewendet — es sei denn, man setzt Klammern. Das kann zum Beispiel bei Operatoren gleicher Priorität zwingend sein. Vorrangregeln sind
Bestandteil der Sprachdefinition — und wenn man sie ignoriert, nicht selten
Anlaß zu großer Verwirrung. Der folgende Versuch, die Funktion max’ anzuwenden, endet mit einer vorerst noch kryptischen Fehlermeldung:
?: max’ 3 * 4 7
ERROR: a -> b -> b is not an instance of class „Num“
Die Ursache ist im Gegensatz zur Fehlermeldung einfach erklärt: Die Funktionsapplikation besitzt höhere Priorität als alle Operatoren. Im vorliegenden Fall
scheitert die Applikation, weil max’ auf das vermeintlich erste Argument 3 und
danach die entstandene Funktion (Currying!) (max 3) auf * angewendet wird.
* ist keine gültige Zahl, also scheitert die Berechnung. Wegen der dominierenden Priorität der Funktionsapplikation sind hier Klammern zwingend:
?: max’ (3 * 4) 7
12
Klammern besitzen Vorrang vor Applikationen und diese vor Operatoren. Dies
ist in HASKELL so festgelegt. Die Priorität der Operatoren selbst kann dagegen
festgelegt werden. Das ist für selbstdefinierte Operatoren sinnvoll, weil man
Klammern spart.
Eine weitere verbreitete Konvention, die Klammern sparen hilft, ergibt sich aus
der Assoziativität eines Operators. Ob a+b+c als (a+b)+c oder als a+(b+c)
ausgewertet wird, ist bekanntlich gleichgültig, denn + ist rechts- und linksassoziativ. Anders verhält es sich mit der Division:
a / (b / c) ≠ (a / b) / c
In der Mathematik hat man die erste der beiden Klammerungen als verbindlich
festgelegt: Die Division assoziiert rechts. Damit sind die Klammern entbehrlich.
assoziativ =
linksassoziativ
und
rechtsassoziativ
DEF.:
EIN OPERATOR ⊗
heißt rechtsassoziativ, wenn gilt : für alle xi
x1 ⊗ x2 ⊗... ⊗ xn = x1 ⊗ (x2 ⊗...( xn-1 ⊗ xn )...))
Ein Operator ist linksassoziativ wenn gilt:
x1 ⊗ x2 ⊗... ⊗ xn = ((...(x1 ⊗ x2 ) ⊗... xn-1 ) ⊗ xn
Ein links- und rechtsassoziativer Operator heißt assoziativ. Er erfüllt das Assoziativgesetz:
x1 ⊗ ( x2 ⊗ x3 )= ( x1 ⊗ x2 ) ⊗ x3
Vers. 0.2 / 10-98
17
Grundlegende Begriffe: Funktionen
Nicht für jeden Operator ist eine Festlegung zwingend erforderlich, allerdings
muß man in diesem Fall explizit Klammern setzen.
2.6 Funktionen höherer Ordnung
Funktionen, die Funktionen als Parameter besitzen oder deren Wertebereich
Funktionen enthält, nennt man Funktionen höherer Ordnung. Nach den Ausführungen zur Curry-Darstellung ist das eigentlich nichts besonders. Hier geht es
aber nicht um implizit auftretende Funktionale, sondern um eine höhere Stufe
der Abstraktion. In einem Ausdruck kann man nicht nur von Werten abstrahieren, sondern auch von ganzen Teilausdrücken — selbst Funktionen. Das folgende Beispiel macht das Prinzip deutlich:
?: abs’ ( quadrat 3.0 - quadrat (3.0 + 0.01) ) < 0.1
True
Abstrahiert man von den Werten, kann man das in mathematischer Scheibweise
so ausdrücken:
| x2 - (x + δ)2 | < ε
oder als Definition in HASKELL:
> genuegendKleinQuadrat x delta epsilon =
>
(quadrat x - quadrat (x+delta) ) < epsilon
Offensichtlich ist wäre es nützlich, auch von der speziellen Funktion quadrat
abstrahieren zu können. Genau das kann man mit Funktionen höherer Ordnung
machen: Der mathematische Ausdruck:
Abstraktion von
Funktionen (oder
Verfahren) führt
zu allgemeineren
Funktionen:
Funktionalen
(Funktionen höher
Ordnung)
| f(x) - f(x + δ) | < ε
kann in HASKELL unmittelbar mit f als Argument — deshalb nennen wir die
Funktion Funktional oder Funktion höherer Ordnung — definiert werden:
> genuegendKlein f x delta epsilon =
>
abs’ (f x - f (x+delta) ) < epsilon
Den oben berechneten Ausdruck mit den Werten x = 3.0, epsilon = 0.01 und
delta = 0.1 schreibt man jetzt einfach:
18
Fallunterscheidungen
?: genuegendKlein quadrat
True
3.0
0.01
0.1
Das Wesen der funktionalen Abstraktion besteht darin, ein allgemeineres Problem zu lösen, von dem das ursprüngliche ein Spezialfall ist.
Abstraktion von Funktionen ist ganz offensichtlich ein kraftvolles Programmierprinzip. Wir werden davon noch oft Gebrauch machen. Später wird von einem
anderen, ebenso wichtigen Prinzip der Entwicklung von Software die Rede sein:
Datenabstraktion, eine Technik zur Abstraktion von der Repräsentation von
Daten.
2.7 Fallunterscheidungen
Fallunterscheidungen
gehören zu den
wichtigsten Konstrukten von Programmiersprachen.
Wir haben bereits im vorigen Abschnitt eine Fallunterscheidung verwendet.
Fallunterscheidungen gehören zu den wichtigsten Sprachkonstrukten, die zur
Definition von Funktionen verwendet werden. Sie finden sich in ähnlicher Form
in allen Programmiersprachen. Um etwas anspruchsvollere Beispiele für Funktionsdefinitionen angeben zu können, sollen Fallunterscheidungungen schon hier
eingeführt werden.
In Programmen ist es wie im richtigen Leben oft nötig, Entscheidungen auf der
Basis von bekannten Tatsachen zu treffen.
Das vielleicht einfachste Programmbeispiel ist der Test, ob ein Wert x kleiner als
0 ist.
Der Absolutwert einer Zahl x berechnet sich z.B. so:
wenn x < 0 dann -x sonst x
Beachte: jeder Zweig der Fallunterscheidung, und damit der gesamte Ausdruck,
liefert einen Wert1. In HASKELL notiert man die Fallunterscheidung folgendermaßen:
> abs’ x =
>
if x < 0 then -x else x
?: abs’ (-7)
7
Wohlgemerkt: Fallunterscheidungen liefern einen Wert und gehören deshalb zu
den Ausdrücken der Sprache. Die Fallunterscheidung hat also die Syntax:
1. In anderen Programmiersprachen ist das nicht immer der Fall.
Vers. 0.2 / 10-98
19
Grundlegende Begriffe: Funktionen
if
<Ausdruck_der_Wahrheitswert_liefert> then <Wert1> else <Wert2>
<Wert1> und <Wert2> müssen den gleichen Typ haben, sonst enthielte der Werteberecht des if-Ausdrucks Elemente unterschiedlichen Typs.
Fallunterscheidungen können in HASKELL auch durch sogenannte Wächter ( | )
ausgedrückt werden. Jeweils eine Bedingung ’bewacht’ einen Ausdruck, der nur
dann für die Berechnung des Werts der Fallunterscheidung verwendet wird,
wenn die Bedingung erfüllt ist. Ist mehr als eine erfüllt, wird der erste Ausdruck
verwendet, für den die Bedingung zutrifft. Deshalb heißen Fallunterscheidungen
auch bedingte Ausdrücke (conditional expressions).
Dazu ein weiteres kleines Beispiel:
> max’ x y
>
| x >= y
>
| x < y
= x
= y
?: max’ (-17) 12
12
Die Fallunterscheidung zur Berechnung der größeren von zwei Zahlen ist vollständig: alle Möglichkeiten sind explizit genannt. In der folgenden Formulierung
ist das nicht so. Alle restlichen Fällen — im Beispiel gibt es nur einen nicht
explizit behandelten Fall, nämlich x ist gleich y — werden hinter otherwise
zusammengefaßt:
> max’’ x y
>
| x > y
= x
>
| x < y
= y
>
| otherwise = x
Fallunterscheidungen sollen möglichst disjunkt und vollständig sein. Eine Fallunterscheidung ist disjunkt, wenn es keine zwei überlappenden Fälle gibt. Beispiele für Überlappung sind : x > 0 und x > 10 oder x >= 0 und x == 0.
Im zweiten Beispiel wird die zweite Alternative x == 0 nie erreicht, da sie im
vorherigen x >= 0 enthalten ist. Manche Übersetzer bzw. Interpretierer von
Programmiersprachen weisen Alternativen, die nicht erreichbar sind, zurück.
PROGRAMMIERSTIL:
Fallunterscheidungen sollen vollständig und disjunkt sein.
Vollständigkeit erzwingt man gegebenenfalls mit otherwise.
Ein Fall darf nicht in einem anderen enthalten sein.
20
Fallunterscheidungen
Eine nützliches, in vielen Programmiersprachen anzutreffendes Konstrukt für
Fallunterscheidungen ist case. Für die Maximumsbestimmung sieht das so aus:
> maxCase x y =
>
case (x >= y) of
>
True -> x
>
False -> y
?: maxCase 53 (-(-232))
232
-- warum soviele Klammern?
Der Wert des nach dem reservierten Wort case stehenden Ausdrucks — hier
(x>=y) — steuert die Alternativen. Jeder mögliche Wert — hier nur True und
False — führt zur Berechnung eines Wertes, dem des gesamten case-Ausdrucks. Das ist in diesem Fall eines der Argumente x und y selbst. Auch für
case ist eine abschließende Bedingung otherwise möglich, die alle restlichen
Fälle abfängt. Die Werte aller Alternativen müssen gleichen Typ haben.1 Dies ist
gleichzeitig der Typ des gesamtem case-Ausdrucks.
Übungen
1.
2.
3.
4.
5.
Definiere die Funktion signum, die -1 liefert, wenn ihr Argument eine negative Zahl
ist, 0 sonst.
Der Ausdruck f x . g y bedeutet:
a f ( x . (g y))
b f (x . g) y
c ((f x) . g) y
d (f x) . (g y)
Definiere unter Verwendung von quadrat eine Funktion hochVier.
Argumentbereich für hochVier seien die ganzen Zahlen, Wertebereich die natürlichen Zahlen. ist die Funktion
a total
b surjektiv
c injektiv?
Welche Funktionen besitzen keine Inverse?
1. Warum?
Vers. 0.2 / 10-98
21
Grundlegende Begriffe: Funktionen
6.
a) Entwickeln Sie eine Haskellfunktion abl , die für eine beliebige differenzierbare
Funktion f :: Float -> Float den Wert der Ableitung an der Stelle x annähert. Der Differenzenquotient an der Stelle x0 für eine differenzierbare Funktion f ist:
(f(x0+dx) - f(x0) ) / dx
b) Schreiben Sie eine Funktion
ablDiff::Float-> Float -> (Float->Float)->(Float->Float)
-> Float-> Float
, die die absolute Differenz der obigen Approximation und der tatsächlichen Ableitung berechnet.
Wenden Sie die Funktion für dx = 0.01 , x = 3.0 auf die folgenden Funktionen an:
(1) f(x) = x2 mit f’(x) = 2*x
(2) f(x) = log x
mit f’(x) = 1/x (Bibliotheksfunktion: log)
(3) f(x) = sqrt(x), x >= 0 mit f’(x) = 1/(2*sqrt(x)) (Bibliotheksfunktion sqrt)
(4) f(x) = ex mit f’(x) = ex (Bibliotheksfunktion exp )
22
Herunterladen