Typen und Prozeduren

Werbung
Programmierung 1 - Repetitorium
WS 2002/2003
Programmierung 1 - Repetitorium
Andreas Augustin und Marc Wagner
Homepage: http://info1.marcwagner.info
Programmierung 1 - Repetitorium
Montag, den 07.04.03
Kapitel 3
Typen und Prozeduren
Programmierung 1 - Repetitorium
3.1 Typen und Typdisziplin
int { 1 , 2 , ... }
real { 3.4 , 5.0 , ... }
unit { ( ) }
bool { true , false }
Die Typdisziplin einer Programmiersprache sorgt dafür,
dass auf Werte nur solche Operationen angewendet werden können,
die die Werte richtig interpretieren.
Eine vereinfachte Typdisziplin von SML ist die monomorphe Typdisziplin :
1.
Jeder Konstante und jedem gebundenen Bezeichner ist genau ein Typ
zugeordnet.
2.
Jedem Operationssymbol sind endlich viele Typen zugeordnet.
3.
Für Operationsausdrücke wird die folgende Typregel verwendet :
a1 : t1
: t1 t2<t
a1
4.
a2 : t2
a2 : t
Für Konditionale wird die folgende Typregel verwendet :
a1 : bool
a2 : t
a3 : t
if a1 then a2 else a3 : t
Programmierung 1 - Repetitorium
3.1 Typen und Typdisziplin
5.
Für Tupelausdrücke wird die folgende Typregel verwendet :
a1 : t1
...
an : tn
(a1,...,an) : t1 ... tn
6.
Für Prozeduranwendungen wird die folgende Typregel verwendet :
p : t1 < t2
a : t1
p a : t2
7.
Die obigen Regeln sorgen dafür, dass jedem zulässigen Ausdruck
genau ein Typ zugeordnet werden kann.
8.
Wenn die Auswertung eines Audrucks, dem der Typ t zugeordnet wurde,
einen Wert liefert, handelt es sich immer um einen Wert des Typs t.
Diese wichtige Eigenschaft der Typdisziplin nennt sich Typsicherheit.
Typen können nicht mit der Menge ihrer Werte identifiziert werden.
Typen legen zusätzlich eine Interpretation für ihre Werte fest.
Programmierung 1 - Repetitorium
3.2 Prozeduren sind Werte
Prozeduren können als Komponenten von Tupeln und als Argumente
und Ergebnisse von Prozeduren vorkommen.
Prozeduren mit prozeduralen Argumenten oder Ergebnissen bezeichnet
man als höherstufige Prozeduren.
Prozeduren lassen sich mit speziellen Ausdrücken (Abstraktionen) beschreiben :
fn (x:int, y:int) => x*y
Abstraktionen sind Ausdrücke, die zu Prozeduren auswerten.
Eine Abstraktion beschreibt eine Prozedur ohne ihr einen Namen zu geben.
val produkt = fn (x:int, y:int) => x*y
Rekursive Prozeduren kann man wegen der Selbstreferenz nicht unmittelbar
mit Abstraktionen beschreiben.
val rec f = fn n:int => if n<1 then 1 else f(n-1)
Eine Abkürzung dafür ist : fun f (n:int) = if n<1 then 1 else f(n-1)
Programmierung 1 - Repetitorium
3.2 Prozeduren sind Werte
Beispiel für den Nutzen prozeduraler Argumente :
sum( f , n) =
n
i =1
f (i )
val rec sum = fn (f:int->int, n:int) =>
if n<1 then 0 else sum(f, n-1) + f(n)
fun sum (f:int->int, n:int) = if n<1 then 0
else sum(f, n-1) + f(n)
Die Summe der ersten 10 Quadratzahlen erhält man hiermit :
sum (fn i:int => i*i, 10)
Programmierung 1 - Repetitorium
3.3 Kartesische und kaskadierte Prozeduren
Prozeduren, deren Argumenttyp ein Produkttyp t1 ... tn ist, sind kartesisch.
plus : int * int -> int
fun plus (x:int, y:int) = x+y
Alternativ : val plus = fn (x:int, y:int) => x+y
plus (3,4)
Prozeduren, deren Ergebnistyp ein Pfeiltyp t1 < t2 ist, sind kaskadiert.
plus : int -> int -> int
fun plus (x:int) (y:int) = x+y
Alternativ : val plus = fn x:int => fn y:int => x+y
plus 3 4
Der Typkonstruktor -> klammert nach rechts.
int -> int -> int
int -> ( int -> int )
Prozeduranwendungen klammern nach links.
plus 3 4
( plus 3 ) 4
Programmierung 1 - Repetitorium
3.4 Polymorphe Prozeduren
Prozeduren, die auf mehr als einen Typ anwendbar sind, sind polymorph.
Sei X eine Menge, f
X < X und n
0. Wir definieren fn
X < X rekursiv :
fn(x) = if n = 0 then x else fn-1(f(x))
Wir deklarieren jetzt eine kaskadierte Funktion iter, die für einen beliebigen
Typ t anwendbar ist und die Funktion f n-mal hintereinander auf x anwendet :
fun iter (f:‘a->‘a) (n:int) (x:‘a) =
if n<1 then x else iter f (n-1) (f x)
Typschema : val iter : (‘a -> ‘a) -> int -> ‘a -> ‘a
Hierbei ist ‘a (gesprochen : alpha) eine sogenannte Typvariable.
Anwendungsbeispiele von iter :
iter (fn x:int => x*x) 4 2
Instanz des Typschemas :
(int -> int) -> int -> int -> int
iter (fn x:real => x*x) 5 2.0
(real -> real) -> int -> real -> real
iter (fn x:bool => not x) 3 true
(bool -> bool) -> int -> bool -> bool
Programmierung 1 - Repetitorium
3.4 Polymorphe Prozeduren
Jedes Typschema hat unendlich viele Instanzen. (aufgrund des Pfeiles)
Ein Bezeichner heißt polymorph, wenn ihm ein Typschema zugeordnet ist.
Ein Ausdruck heißt polymorph, wenn man ihm mehrere Typ zuordnen kann.
Ein Ausdruck heißt expansiv, wenn er eine Prozedur- oder eine OperationsAnwendung enthält, die nicht innerhalb einer Abstraktion steht.
Man unterscheidet folgende Arten von Deklarationen :
1.
Monomorphe Deklaration : Wenn a ein monomorpher Ausdruck mit dem
Typ t ist, wird x monomorph mit dem Typ t typisiert.
Beispiel :
fun inc x = x+1
val inc : int -> int
2.
Polymorphe Deklaration : Wenn a ein polymorpher Ausdruck ist, der nicht
expansiv ist, wird x polymorph mit dem Typschema typisiert, dass alle Typen
von a beschreibt.
Beispiel :
fun id (x:‘a) = x
val id : ‘a -> ‘a
Programmierung 1 - Repetitorium
3.4 Polymorphe Prozeduren
3.
Ambige Deklaration : Wenn a ein polymorpher und expansiver Ausdruck ist,
wird x monomorph mit einem der dem Ausdruck a zugeordneten Typen
typisiert. Dabei wird der Typ so gewählt, dass er mit den nachfolgenden
Verwendungen des Bezeichners x verträglich ist (wenn möglich).
Beispiel :
val f = id id
val f : ‘b -> ‘b (Warning!)
Bei rekursiven Deklarationen und Prozedurdeklarationen kann der ambige Fall
nicht auftreten ! (rechte Seite ist Abstraktion und somit nicht expansiv)
fun f (x:int) = f x
val f : int -> ‘a
Der Bezeichner f wird polymorph mit dem Schema
‘a (int -> ‘a)
typisiert, da als Ergebnistyp von f jeder Typ zulässig ist.
Programmierung 1 - Repetitorium
3.5 Typinferenz
Das automatische Herleiten von fehlenden Typconstraints nennt man Typinferenz.
fun sum f n = if n<1 then 0 else sum f (n-1) + f n
val sum : (int -> int) -> int -> int
Bei überladenen Operationssymbolen gibt die Typinferenz int den Vorzug.
fun plus (x,y) = x+y
val plus : int * int -> int
Typinferenz berechnet immer die allgemeinsten Typconstraints.
fun f(x,y) = (2*x,y)
val f : int * ‘a -> int * ‘a
Durch die Angabe von expliziten Typconstraints können bei größeren Programmen
Typfehler schneller lokalisiert werden und diese Fehler direkt bei der Deklaration
diagnostiziert werden.
Programmierung 1 - Repetitorium
3.6 Op-Ausdrücke
Op-Ausdrücke sind Abkürzungen für Abstraktionen, die Operationen als
Kartesische Prozeduren zur Verfügung stellen.
op+
fn (x,y) => x+y
op<
fn (x,y) => x<y
op=
fn (x,y) => x=y
op div
fn (x,y) => x div y
op ~
fn x => ~x
Da die meisten Operationssymbole überladen sind, ist das Weglassen der
Typconstraints für die Argumentvariablen der Abstraktionen wesentlich.
Programmierung 1 - Repetitorium
3.7 Typen und Gleichheit
Typen, für deren werte ein Test auf Gleichheit mit = möglich ist, heißen
Typen mit Gleichheit.
Die Typen int, bool und unit sind Beispiele für Typen mit Gleichheit.
Dagegen sind die Pfeiltypen t1 < t2 Typen ohne Gleichheit.
Auf Produkttypen t1 ... tn ist der Gleichheitstest genau dann zulässig,
wenn er auf allen Komponenten t1 , ... , tn zulässig ist.
Um die Unterscheidung zwischen Typen mit und ohne Gleichheit in Typschemata
ausdrücken zu können, gibt es zwei Arten von Typvariablen :
Typvariablen mit nur einem Hochkomma ‘a für beliebige Typen
Typvariablen mit zwei Hochkommas ‘‘a nur für Typen mit Gleichheit
Programmierung 1 - Repetitorium
3.8 Bezeichnerbindung
1.
Dynamische Bezeichnerbindungen (bei Ausführung einer Phrase) :
val f = fn x => x*x
f 5
Die Ausführung der Anwendung bindet die Argumentvariable x dynamisch
an die Zahl 5.
2.
Statische Bezeichnerbindungen (bei semantischer Analyse einer Phrase) :
val f = fn x => x*x
Die Analyse der Deklaration bindet den Bezeichner f statisch an den
Typ int -> int und den Bezeichner x statisch an int.
3.
Lexikalische Bezeichnerbindungen :
Lexikalische Bindungen beschreiben den strukturellen Rahmen für
statische und dynamische Bindungen.
Programmierung 1 - Repetitorium
3.8 Bezeichnerbindung
Für das Auftreten (Vorkommen) von Bezeichnern gibt es zwei Möglichkeiten :
definierend und benutzend
Definierende Auftreten führen eine Bindung ein.
Benutzende Auftreten benutzen eine bestehende Bindung.
Benutzende Auftreten, die innerhalb der betrachteten Phrase lexikalisch
ungebunden sind, werden als freie Auftreten bezeichnet.
fn x => fn y => z x (y x)
Die überstrichenen Bezeichner sind definierende Auftreten,
die anderen sind benutzende Auftreten.
Der Bezeichner z ist hier frei vorkommend, da er keinem definierenden Vorkommen
von z innerhalb der betrachteten Phrase zugeordnet werden kann.
Programmierung 1 - Repetitorium
3.8 Bezeichnerbindung
Die lexikalischen Bindungen lassen sich auch textuell darstellen.
Alle Bezeichnerauftreten einer Bindungsgruppe werden dabei mit demselben
Index markiert. Dagegen bekommen freie Bezeichner keinen Index.
Mehrere definierende Auftreten desselben Bezeichner werden mit verschiedenen
Indizes versehen.
fn x => fn y => (fn y => (fn x => z x y) x) y
Lexikalische Bindungen textuell dargestellt :
fn x1 => fn y1 => (fn y2 => (fn x2 => z x2 y2) x1) y1
Diese Phrase lässt sich nun wie folgt bereinigen (konsistente Umbenennung) :
fn x => fn y => (fn u => (fn v => z v u) x) y
Bei einer Abstraktion fn x => a ist der Gültigkeitsbereich des definierenden
Auftretens von x der Ausdruck a. Alle benutzenden Auftreten von x, die lexikalisch
an dieses definierende Auftreten von x gebunden sind, müssen sich innerhalb des
Ausdrucks a befinden.
Programmierung 1 - Repetitorium
3.9 Prozeduren und Auswertungsprotokolle
Wenn ein Ausdruck keine freien Bezeichner enthält, heißt er geschlossen,
sonst nennt man ihn offen.
Offene Ausdrücke sind unvollständige Beschreibungen, die nur im Kontext von
externen Bezeichnerbindungen interpretiert werden können.
val x = 1
fun inc y = x+y
liefert die dynamischen Bindungen
x + 1
inc + fn y => 1+y
Eine Redeklaration des Bezeichners x hat damit keine Auswirkung auf die bereits
Berechnete Prozedur inc :
val x = 5
liefert die dynamischen Bindungen
x + 5
inc + fn y => 1+y
(die Bindung von inc ist also unverändert)
Programmierung 1 - Repetitorium
3.9 Prozeduren und Auswertungsprotokolle
fun f x y = if x = 0 then y else y + f (x-1) y
führt folgende dynamische Bindung ein :
f + rec f => fn x => fn y => if x=0 then y else y + f (x-1) y
Das Auswertungsprotokoll für den Ausdruck f 1 5 ergibt :
f 1 5
+
+
+
+
+
+
+
+
+
+
+
(wobei
die rekursive Abstraktion ist)
1 5
(fn y => if 1=0 then y else y +
(1-1) y) 5
if 1=0 then 5 else 5 +
(1-1) 5
if false then 5 else 5 +
(1-1) 5
5 +
(1-1) 5
5 +
0 5
5 + (fn y => if 0=0 then y else y +
(0-1) y) 5
5 + (if 0=0 then 5 else 5 +
(0-1) 5)
5 + (if true then 5 else 5 +
(0-1) 5)
5 + 5
10
Herunterladen