Prof. Dr. A. Poetzsch-Heffter Dipl.-Inform. J.O. Blech Dipl.-Inform. M.J. Gawkowski Dipl.-Inform. N. Rauch Technische Universität Kaiserslautern Fachbereich Informatik AG Softwaretechnik Lösungen zum Übungsblatt 3: Softwareentwicklung I (WS 2006/07) Aufgabe 1 Operatorpräzedenzen a) fun f (x,y) = ((x + (y * y)) >= x) Der Bezeichner * hat höhere Präzedenz als + und der Bezeichner + hat höhere Präzedenz als >=. b) fun m (a, b) = ((b + (a div b)) = (b mod a)) Die Bezeichner div und mod haben höhere Präzedenzen als + und = und der Bezeichner + hat höhere Präzedenz als =. c) f : int * int bool. Da y und x als Argumente der Operatoren * und + auftretten, und diese vom Typ m : int * int int sind, müssen sowohl x und y als auch die Ausdrücke y * y und (x + (y * y)) den Typ int besitzen. Da die Ausdrücke (x + (y * y)) und x den gleichen Typ besitzen und als Argumente des Infix-Operators >= auftreten, muss der Ausdruck (x + (y * y)) >= x vom Typ bool sein. m : int * int bool. Da a und b als Argumente der Operatoren div und mod auftreten und diese vom Typ int * int int sind, müssen sowohl a und b als auch die Ausdrücke (b + (a div b)) und (b mod a) vom Typ int sein. Da das Resultat der Addition zweier Zahlen vom Typ int vom Typ int ist, muss ebenfalls der Ausdruck (b + (a div b)) den Typ int besitzen. Da die Ausdrücke (b + (a div b)) und (b mod a) den gleichen Typ besitzen und als Argumente des Infix-Operators = auftreten, muss der Ausdruck (b + (a div b)) = (b mod a) vom Typ bool sein. Aufgabe 2 Syntax-, Typisierungs- und Laufzeitfehler a) Liefert das Ergebnis 12.104 : real. b) Liefert einen Typisierungsfehler, da der Ausdruck auf der linken Seite des Gleichheitszeichens vom Typ int ist, der Ausdruck auf der rechten Seite aber den Typ real besitzt. c) Liefert das Ergebnis "reit nie tot ein tier". d) Liefert das Ergebnis true. e) Liefert das Ergebnis false. f) Liefert das Ergebnis true. g) Liefert das Ergebnis true. h) Liefert das Ergebnis false. i) Liefert einen Typisierungsfehler. In dem Ausdruck (fn x => (x + 1))[] wird versucht eine Funktion vom Typ int int auf eine leere Liste anzuwenden. j) Liefert einen Typisierungfehler, da die Ausdrücke 2 und false nicht den gleichen Typ haben. k) Liefert einen Typisierungsfehler, da die Vergleichsoperation < nur für zwei Argumente des gleichen Typs definiert ist. l) Liefert einen Typisierungsfehler, da das erste Argument im if-then-else-konstrukt vom Typ bool sein muß. m) Liefert das Ergebnis "bibabu". n) Liefert das Ergebnis false.
o) Liefert einen Typisierungsfehler, da der Operator not linksassoziativ ist. Der Ausdruck (not not true) wird von links nach rechts gelesen, als wäre er so geklammert: ((not not) true). Hier, in den innersten Klammern, wird versucht die Funktion not auf not anzuwenden, obwohl not eine Funktion ist, die einen Wert vom Typ bool als Argument erwartet. Dagegen liefert der geklammerte Ausdruck: (10 mod 2, true) = (5-(4+1), if true then (not (not true)) else false) das Ergebnis true. p) Liefert einen Deklarationsfehler, da die Variable x nicht deklariert wurde. q) Liefert einen Syntaxfehler, da das Zeichen? kein Bestandteil von Bezeichnern sein darf. r) Liefert das Ergebnis 0.001. s) Liefert einen Typisierungsfehler, da die Gleichheitsoperation = für den Typ real nicht definiert ist. Hinweis: Dagegen liefert der Ausdruck Real.==(1.0e~3, 0.001) das Ergebnis true. t) Liefert das Ergebnis 3.14. Aufgabe 3 Die Datenstruktur String 13 fun dual2decimal (str:string): int = 14 if ((size(str)) = 0) 15 then 0 16 else (if ((substring(str,(size(str))-1,1))="1") 17 then (2*(dual2decimal(substring(str,0,(size(str)) -1)))) + 1 18 else (2*(dual2decimal(substring(str,0,(size(str)) -1)))) 19 ); Aufgabe 4 Rekursion a) Die Funktion fakultaet 29 fun fakultaet n = 30 if n=0 then 1 31 else fakultaet (n-1) * n b) Die Funktion ngeraden 41 fun ngeraden n = 42 if n=0 then [] 43 else ngeraden(n-1) @ [(n-1)*2] c) Die Funktion power 52 fun power (j:int, k:int) : int = 53 if k = 0 then 1 54 else j * power (j, k-1) d) Die Funktion findapprox 63 fun findappr_helper (d:real,n:real,l:real list,i:int) = 64 if i=((length l)-1) then (~1,~1) 65 else if abs(list.nth(l,i)-n) < d 66 then (0,i) 67 else findappr_helper(d,n,l,i+1) 68 69 fun findapprox (d:real, n:real, l:real list) = 70 if null(l) then (~1,~1) 71 else findappr_helper(d,n,l,0) e) Die Funktion log2: 2
80 fun odd n = if n=0 then false 81 else (if n=1 then true 82 else even(n-1)) 83 and even n = if n=0 then true 84 else (if n=1 then false 85 else odd(n-1)) 86 87 fun gerade x = 88 if (x>0.0) 89 then ungerade(x-1.0) - (1.0/x) 90 else 0.0 91 and ungerade x = 92 gerade (x-1.0) + (1.0/x) 93 94 fun log2 n = 95 if even n 96 then gerade (Real.fromInt n) 97 else ungerade (Real.fromInt n) Aufgabe 5 Binärer Zähler 116 fun singletick (i:bool,l:bool list): bool list = 117 if (null(l)) 118 then (if (i=true) 119 then [true] 120 else [] 121 ) 122 else (if (i=true) 123 then (if (hd(l)=false) 124 then (true::(tl(l))) 125 else (false::(singletick(true,tl(l))))) 126 else (if (hd(l)=false) 127 then (false::(tl(l))) 128 else (true::(tl(l)))) 129 ); 130 131 fun tick(l:bool list): bool list = (rev(singletick(true,rev l))); Aufgabe 6 Terminierung a) Die Funktion find1: 140 fun find_helper1(n,e,l) = 141 if e=list.nth(l,n) 142 then n 143 else find_helper1(n+1,e,l) 144 145 fun find1 (e,l) = find_helper1(0,e,l) 146 147 - find1 (3,[]); 148 149 uncaught exception subscript out of bounds 150 raised at: boot/list.sml:47.35-47.44 151 - find1 (3,[2]); 152 153 uncaught exception subscript out of bounds 154 raised at: boot/list.sml:47.35-47.44 155 - find1 (3,[2,4,5,6]); 156 157 uncaught exception subscript out of bounds 158 raised at: boot/list.sml:47.35-47.44 159 - Betrachten wir als Beispiel einen Aufruf auf die Funktion find1 mit dem Parameterwert (3, [2, 4, 5, 6]), find1(3, [2, 4, 5, 6]). Die Auswertung des Ausdrucks find1(3, [2, 4, 5, 6]) verläuft dann folgendermaßen: find1(3, [2, 4, 5, 6]) = find_helper1(0, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 0) then 0 then find_helper1(1, 3, [2, 4, 5, 6]) = find_helper1(1, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 1) then 1 then find_helper1(2, 3, [2, 4, 5, 6]) = find_helper1(2, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 2) then 2 then find_helper1(3, 3, [2, 4, 5, 6]) = find_helper1(3, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 3) then 3 then find_helper1(4, 3, [2, 4, 5, 6]) = find_helper1(4, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 4) then 4 then find_helper1(5, 3, [2, 4, 5, 6]) = find_helper1(5, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 5) then 5 then find_helper1(6, 3, [2, 4, 5, 6]) = find_helper1(6, 3, [2, 4, 5, 6]) = if 3 = List.nth([2, 4, 5, 6], 6) then 6 then find_helper1(7, 3, [2, 4, 5, 6]) = find_helper1(7, 3, [2, 4, 5, 6]) =... 3
Wie man an diesem Beispiel sieht, gibt es Eingaben (e, l) gibt, für welche die Auswertung des Ausdrucks find1(e, l) nicht terminieren kann: Die Auswertung von find1(e, l) startet mit der Auswertung des Ausdrucks find_helper1(0, e, l) und für jeden Auswertungsschritt i gilt, dass Auswertung von find_helper1(i, e, l) mit dem rekursiven Aufruf von find_helper1(i+1, e, l) endet. In der Praxis sieht dies anders aus. Der Aufruf von find1(e, l) terminiert in ML immer. Allerdings wird die Berechnung des Wertes find1(e, l) für bestimmte Eingaben vorzeitig abgebrochen und statt des Ergebnises wird die Ausnahme subscript out of bounds geliefert. Es gibt natürlich auch Ausdrücke, deren Auswertung in ML nicht terminiert. Z.B. in dem unten dargestellten Beispiel terminiert die Auswertung des Bezeichners semaphor nicht. fun semaphoroeffne s = (print "semaphoroeffne\n" ; if s then semaphorschliesse (not s) else false) and semaphorschliesse s = (print "semaphorschliesse\n" ; if s then true else semaphoroeffne (not s) ) val semaphor = semaphoroeffne true; Ein anderer Grund für die Terminierung der Auswertung eines Ausdrucks, der theoretisch nicht terminierend ist, ist die Beschränktheit von Hardwareressourcen, z.b. die Größe des verfügbaren Arbeitsspeichers und der Festplatte. Zum Beispiel verbrauchte die Auswertung des Bezeichners testtliste in dem unten aufgeführten Beispiel schon nach wenigen Minuten 75% des Arbeitsspeichers auf einem standard Desktop-Rechner. Danach hat sich die Auswertung von testliste dramatisch verlangsamt, da die Liste nicht mehr in den frei verfügbaren Platz im Arbeitsspeicher passte und musste immer wieder das Betriebssystem zwischen der Festplatte und Arbeitsspeicher hin und her verschoben werden (sog. swapping). Da der frei verfügbarer Platz auf der Festplatte auch beschränkt ist, ist klar, dass irgendwann die Auswertung von testliste vom Betriebssystem unterbrochen werden müsste. fun f x = x; val meineliste = List.tabulate(1000000,f); val fertig = false fun erzeugeliste (l1,l2) = (print (Int.toString(length(l2))^"\n"); if fertig then l2 else erzeugeliste(l1,(l1@l2))) val testliste = erzeugelist(meineliste,[]); Im Folgenden werden wir formal die Menge A der Eingaben (e, l) mit der Eigenschaft, dass die Auswertung von find1(e, l) theoretisch nicht terminiert, definieren. Die Auswertung von find1(e, l) terminiert nicht, wenn die Eingabe (e, l) eine der folgenden Bedingungen erfüllt. 1. l = [], d.h. e ist beliebig und l ist eine leere Liste. 2. l [] i. 0 i < length(l) e = List.nth(l, i), d.h. e ist beliebig und l ist eine nicht leere Liste und das Element e kommt in der Liste l nicht vor. Da jede leere Liste l auch die Bedingung i. 0 i < length(l) e = List.nth(l, i) erfüllt, reicht es, wenn wir A wie folgt formulieren A = {(e, l) e ist eine ganze Zahl und l ist eine Liste von ganzen Zahlen und i. 0 i < length(l) e = List.nth(l, i) }. b) Die Funktion find2: 171 fun find_helper2(n,e,l) = 172 if (e/list.nth(l,n))<1.0 173 then n 174 else find_helper2(n+1,e,l) 175 176 fun find2 (e,l:real list) = find_helper2(0,e,l) 177 178 - find2(3.0,[]); 179 180 uncaught exception subscript out of bounds 181 raised at: boot/list.sml:47.35-47.44 182 - find2(3.0,[3.4]); 183 val it = 0 : int 184 - find2(3.0,[2.0,3.4]); 185 val it = 1 : int 186 - find2(3.0,[0.0,2.0,3.4]); 187 val it = 2 : int 188 - find2(3.0,[0.0,2.0]); 189 190 uncaught exception subscript out of bounds 191 raised at: boot/list.sml:47.35-47.44 192-4
Es gilt hier die gleiche Überlegung wie in der vorherigen Teilaufgabe. Der Aufruf von find2(e, l) terminiert in ML immer. Allerdings wird die Berechnung des Wertes find2(e, l) für bestimmte Eingaben vorzeitig abgebrochen und statt des Ergebnises wird die Ausnahme subscript out of bounds geliefert. Dies passiert, wenn die Eingabe (e, l) eine der folgenden Bedingungen erfüllt. 1. l = [], d.h. e ist beliebig und l ist eine leere Liste. 2. l [] i e. 0 i < length(l) e = List.nth(l, i) e < e, d.h. e ist beliebig l ist nicht eine leere Liste, in der kein Element größer als e vorkommt. Interesanterweise, stellen hier Listen-Elemente mit dem Wert gleich 0.0, kein Problem dar, da das Ergebnis der Auswertung des Ausdrucks e/0.0 in ML gleich inf für 0 e beziehungsweise inf für 0 > e ist. Es wird dabei keine Ausnahme geliefert. Die Menge A der Eingaben (e, l) mit der Eigenschaft, dass die Auswertung von find2(e, l) theoretisch nicht terminiert, ist wie folgt definiert A = {(e, l) e ist eine reelle Zahl und l ist eine Liste reeller Zahlen und i e. 0 i < length(l) e = List.nth(l, i) e < e }. 5