Christian-Albrechts-Universität zu Kiel Institut für Informatik Lehrstuhl für Programmiersprachen und Übersetzerkonstruktion Prof. Dr. Michael Hanus, Sandra Dylus, Björn Peemöller 6. Klausur zur Vorlesung „Fortgeschrittene Programmierung“ SS 15 Hinweis: Sie können sich hier ein paar Statisken zur Klausur ansehen. Diese Klausur besteht aus 6 Aufgaben auf 2 Seiten. Gesamtpunktzahl: 65 Punkte, Punktzahl zum Bestehen: 30 Punkte. Einlesezeit: 15 Minuten, Bearbeitungszeit: 120 Minuten. Bitte schreiben Sie auf jedes Blatt, das Sie abgeben, Ihren Namen und Ihre Matrikelnummer! Alle Aufgaben können unabhängig von einander bearbeitet werden, insbesondere auch einzelne Aufgabenteile. Hierbei können Definitionen aus vorherigen Aufgabenteilen verwendet werden, auch wenn diese nicht gelöst wurden. Aus der Vorlesung bekannte Konstrukte (Prelude von Haskell, Prolog-Prädikate) dürfen verwendet werden. Hinweis: Die Einsichtnahme in die korrigierten Klausuren findet am Dienstag, den 03.11., von 10:00 bis 11:00 Uhr im CAP4 (Hochhaus), Raum 715 statt. Bitte bringen Sie dazu einen Lichtbildausweis mit. Viel Erfolg! Aufgabe 1 - Niemand mag: [ ] Java; [ ] Prolog; [ ] Haskell; [ ] Klausuren schreiben 15 Punkte Es könnte/n alle, mehrere, eine oder gar keine Antwort richtig sein. Schreiben Sie die Ihrer Meinung nach richtigen Antworten zu jeder Frage auf; sollte keine der Antwortmöglichkeiten zutreffen, formulieren Sie dies bitte explizit. Markierungen auf dem Aufgabenzettel werden nicht berücksichtigt! Eine Abgabe könnte also beispielhaft wie folgt aussehen: 21. 22. 23. 24. 25. a,b,c keine Antwortmöglichkeit ist korrekt a d, e a,b,c,d,e Richtig zugeordnete Antworten ergeben Pluspunkte, falsch zugeordnete Antworten Minuspunkte; jede Frage kann dabei mit minimal 0 Punkten und maximal 5 Punkten bewertet werden. Die Gesamtpunktzahl wird am Ende durch 5 geteilt und abgerundet. Java 1. Welche der folgenden Programm-Fragemente kompilieren? a) LinkedList<?> list1 = null; LinkedList<Integer super ?> list2 = null; list2 = list1; 1 b) LinkedList<?> list1 = null; LinkedList<? extends ?> list2 = null; list2 = list1; c) LinkedList<?> list1 = null; LinkedList<? extends Integer> list2 = null; list2 = list1; d) LinkedList<?> list1 = null; LinkedList<? super Integer> list2 = null; list2 = list1; e) LinkedList<?> list1 = null; LinkedList<? super Integer> list2 = null; list1 = list2; 2. Welche der folgenden Generic-Konstrukte beschreiben Bounded Wildcards? a) b) c) d) e) <? <C <? <? <_ super B> super ?> extends A> implements C> super C> 3. Welcher Thread wacht beim Aufruf von o.notify() auf? a) b) c) d) e) Der zuletzt schlafengelegte Thread von Objekt o wacht auf. Mittels der Übergabe der Thread-ID kann ein bestimmter Thread geweckt werden. Es wachen alle Threads von Objekt o auf und bewerben sich neu um das Lock. Der zuerst schlafengelegte Thread von Objekt o wacht auf. Es wird ein beliebiger der systemweit schlafenden Threads geweckt. 4. Wann wird ein Lock auf ein Objekt wieder freigegeben? a) b) c) d) e) Beim Beim Beim Beim Beim Verlassen der synchronisierten Methode Aufruf der Methode wait() auf ein anderes Objekt Aufruf der Methode notify() Aufruf der Methode wait() Aufruf der Methode waitAll() 5. Welche der Aussagen gelten für Java Generics? a) b) c) d) e) Java Generics bezeichnet die Umsetzung von parametrischem Polymorphismus. Es sind explizite Typecasts erforderlich. Typfehlermeldungen könnten bereits zur Compile-Zeit ausgegeben werden. Der Diamantoperator <> kann dabei immer verwendet werden, um den richtigen Typ zu inferieren. Es ist keine Typsicherheit gegeben. Prolog 6. Gegeben sei das folgende Prolog-Programm. swap([A,B|L], [B,A|L]) :- B < A. swap([A|L], [A|N]) :- swap(L, N). Was ist das Ergebnis der Anfrage ?- swap([1,8,1,1],X).? 2 a) b) c) d) e) X = [8,1,1,1] X = [1,8,1,1] false X = [1,1,8,1] X = [1,1,1,8] 7. Welche der Zeichenfolgen sind valide Listen in Prolog? a) b) c) d) e) [[4] | [5]] [42, prolog | []] [3 | 17] [11,12] [1,2,[3]] 8. Welche der Anfragen werden mit true beantwortet? a) b) c) d) e) ?????- 121 is 11 * 11. 8 * 7 is 56. 3 * 7. 12 - 8 is 2 + 2. 4 + 5 is 4 + 5. 9. Vervollständigen Sie: σ heißt allgemeinster Unifikator, falls für alle Unifikatoren σ 0 eine Substitution φ existiert mit a) b) c) d) e) φ = σ ◦ σ0 σ = σ0 ◦ φ σ0 = σ ◦ φ σ0 = φ ◦ σ σ = φ + σ0 10. Welche der Aussagen über Prolog sind richtig? a) b) c) d) e) Die Auswertungsstrategie basiert auf Tiefensuche. Prolog ist eine Untermenge der Prädikatenlogik 1. Stufe. Das Komma (,) entspricht dem logischen Und (∧) und das Semikolon (;) dem logischen Oder (∨). Atome enthalten nur Kleinbuchstaben. Die Implementierung von Relationen ist in Prolog in Form von Funktionen möglich. Haskell 11. Der Ausdruck [(1,True),(0,False)] kann wie folgt ohne weitere Hilfsdefinitionen getypt werden: a) b) c) d) e) [(Int,Bool)] [Pairs] Ord a => [(a,Bool)] [Int,Bool] [(Int,Bool),(Int,Bool)] 12. Die Berechung des Ausdrucks [(y,x) | x <- [1..3], y <- [1..2]] ergibt: a) b) c) d) [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)] [(1,1),(2,2),(1,3),(2,1),(1,2),(2,3)] [(1,2),(2,1),(3,2),(1,1),(2,2),(3,1)] [(1,1),(1,2),(2,1),(2,2),(3,1),(3,2)] 3 e) [(1,1),(2,1),(3,1),(1,2),(2,2),(3,2)] 13. Die Funktion nonsense x y = x y y kann wie folgt getypt werden: a) b) c) d) e) a -> a -> a (b -> b -> a) (a -> b -> b) (a -> a -> a) (a -> a -> b) -> -> -> -> b b a a -> -> -> -> a a a b 14. Der Ausdruck Branch (Tip 1) (Tip 2) ist ein Wert der folgenden Datentypen: a) b) c) d) e) data data data data data Tree Tree Tree Tree Tree a a a a a = = = = = Tip Tip Tip Tip Tip a | Branch a a a | Branch (Tree a) (Tree a) (Tree a) | Branch Int Int (Tree a) | Branch (Tree a) (Tree a) Int | Branch (Tree a) (Tree a) 15. Welche der folgenden Listen sind in Haskell nicht valide (ergeben einen Typfehler)? a) b) c) d) e) [[],[]] [(),(1),(2)] [[1],[False]] [False, [True]] [[1,2],[]] Aufgabe 2 - Hobby: Constraint- und Metaprogrammierung 10 Punkte Abbildung 1: Quelle - http://xkcd.com/287/ 1. Modellieren Sie das in Abbildung 1 dargestellte Problem mit Hilfe von Prolog und Finite Domain Constraints. Definieren Sie also ein Prädikat xkcd, das alle möglichen Vorspeisekombinationen berechnet, die in der Summe ihrer Preise $15.05 ergeben. Dabei soll der Aufruf ? - xkcd([Fruits,Fries,Salad,Wings,Sticks,Plate]). die Anzahl der Portionen der jeweiligen Vorspeise ausgeben. 2. In der Vorlesung haben Sie folgenden Meta-Interpretierer für Prolog betrachtet: prove(true) :- !. % Rumpf von Fakten prove( (A,B) ) :- !, prove(A), % Beweise 1. Teilziel prove(B). % Beweise 2. Teilziel prove(A) :clause(A,G), % Suche passende Klausel prove(G). % Beweise Klauselrumpf 4 Erweitern Sie diesen Meta-Interpretierer so, dass dieser bei einer erfolgreichen Ableitung die Liste aller der dabei bewiesenen Literale zurückgibt. So soll z.B. für das Programm p(X) :- q(X). q(a). die Erweiterung zu proveList die folgenden Ergebnisse liefern. ?- proveList(p(X),Ls). Ls = [p(a),q(a)] X = a. Hinweis: Mit dem Prädikat clause(H,B) kann auf die vorhandenen Klauseln zugegriffen werden. Dieses Prädikat ist beweisbar, falls die Klausel H :- B. existiert, wobei H keine Variable sein darf. 10 Punkte Aufgabe 3 - Refactoring 1. In dieser Teilaufgabe sollen Sie die folgenden “Meisterwerke”, die von Ihren Kommilitonen (so oder so ähnlich) als erste Haskell-Programme abgegeben wurden, mit Hilfe von Pattern Matching und “scharfem Hinsehen” vereinfachen. Die vereinfachte Version soll dabei weder if-then-else-Sequenzen noch Guards verwenden. ggT :: Integer -> Integer -> Integer ggT a b = if b==0 then a else ggT b (mod a b) isPrefixOf :: Eq a => [a] -> [a] -> Bool isPrefixOf x (y:ys) = if length x == 0 then True else if head x == y then isPrefixOf (tail x) ys else False 2. Die folgenden Funktionen funktionieren zwar einwandfrei, können aber einen Feinschliff gebrauchen. Definieren Sie die Funktionen mit Hilfe von List Comprehension um. catJusts :: [Maybe a] -> [a] catJusts [] = [] catJusts (m:ms) = case m of Just x -> x : catJusts ms Nothing -> catJusts ms allPairs :: [a] -> [b] -> [(a,b)] allPairs xs ys = concatMap (\x -> map (\y -> (x,y)) ys) xs 3. In der Eile wurden im folgenden Code die Typsignaturen vergessen. Geben Sie den allgemeinsten Typ für die jeweilige Funktion an. fa [] y = [y] fa (x:xs) y = case y <= x of True -> y : x : xs False -> x : fa xs y fb a b c d = if a d then b else c 5 fc x y = x fd [] g z = z fd (x:xs) g z = fd xs g (g z x) fe x y Nothing = y fe x y (Just v) = x v 10 Punkte Aufgabe 4 - Fahrkarten, bitte! In dieser Aufgabe sollen Sie einen Datentyp (sowie jegliche Hilfstypen, die Sie benötigen) für Fahrkarten sowie Funktionen auf diesem Datentyp definieren. Geben Sie in allen Fällen die Typsignatur für definierte Funktionen an. 1) Definieren Sie einen Datentyp Ticket für Fahrkarten. Es sollen dabei verschiedene Arten von Fahrkarten repräsentiert werden können: • Eine Zugfahrtkarte von der Startstadt zur Zielstadt; Erste oder Zweite Klasse ist möglich • Eine Busfahrtkarte von der Startstadt zur Zielstadt (sowas wie ein Fernbus) Aus Gründen der Typsicherheit sollen Städte und Ticketklassen dabei nicht als String repräsentiert werden. Als Städte sollen dabei Hamburg, Kiel, Frankfurt und Freiburg möglich sein. 2) Geben Sie zwei Werte für den Datentyp Ticket an. Dabei soll jede Fahrkartenart (Zug- und Busticket) einmal verwendet werden. 3) Eine Reise wird durch eine Liste von Fahrtkarten repräsentiert: type Journey = [Ticket]. Eine Reise ist genau dann valide, wenn für je zwei aufeinanderfolgende Tickets in der Liste gilt: die Zielstadt der ersten Fahrkarte ist die Startstadt der zweiten Fahrkarte. Definieren Sie nun ein Prädikat valid :: Journey -> Bool{haskell}, das erfüllt ist, wenn eine Reise gültig ist. Sinnvollerweise besteht eine Reise aus einer mindestens zwei-elementigen Liste von Fahrtkarten; die Randfälle (leere und einelementige Liste) sollen das Ergebnis True liefern. 4) Geben Sie eine valide Show-Instanz für Ihren Datentyp Ticket an, der die Informationen des Datentypens schemenhaft wie folgt als String darstellt: Fahrzeug (Klasse): Startstadt ~> Zielstadt Hinweis: Bedenken Sie, dass Datentypen, die innerhalb ihrer Definition vorkommen, auch eine ShowInstanz benötigen, wenn diese nicht schon vorhanden sein sollte. 10 Punkte Aufgabe 5 - Nichtdeterministisches Sortieren Das folgende Prologprogramm definiert ein Prädikat bubble(XS,YS). Das zweite Argument stellt dabei die aufsteigend sortierte Liste des ersten Arguments dar und wird dabei mit Hilfe eines nichtdeterministischen Bubblesort-Algorithmus sortiert. bubble(L, R) :- swap(L, N), bubble(N, R). bubble(L, L). swap([A,B|L], [B,A|L]) :- B < A. swap([A|L], [A|N]) :- swap(L, N). 1. Zunächst betrachten wir das Prädikat swap etwas genauer. Bearbeiten Sie eine der folgenden Aufgaben, um die Anfrage ?- swap([4,3,2],Xs). zu beweisen. i) Geben Sie für die Anfrage den entstehenden SLD-Baum an, der durch die SLD-Resolution aus der Vorlesung entsteht. 6 ii) Beweisen Sie die Anfrage mit der Auswertungsstrategie von Prolog. 2. Betrachten wir folgende Anfrage von bubble: ?- bubble([2,3,1],X). X = [1, 2, 3]; X = [2, 1, 3]; X = [2, 3, 1]. Durch die nichtdeterministische swap-Funktion und der Überlappung der ersten und zweiten Regel in bubble erhalten wir durch Backtracking insgesamt drei Lösungen, da jede teilsortierte Liste in Relation mit der Originalliste steht. Modifizieren Sie die obige Implementierung durch das Einfügen eines Cut-Operators (!) so, dass das Prädikat bubble letztendlich nur noch in Relation mit ihrer sortierten Liste steht. Es dürfen keine anderen Änderungen vorgenommen werden. So soll z.B. die Anfrage ?- bubble([2,3,1],X) nur noch X = [1,2,3] als Ergebnis liefern. Skizzieren Sie dabei grob, in welchem Schritt der Cut-Operator in der Anfrage auftaucht und in welchem Schritt welche Berechnungen wegfallen. Aufgabe 6 - Die Philosophie ist eine Art Rache an der Wirklichkeit – Friedrich Nietzsche 10 Punkte Betrachten Sie die folgende, fehlerhafte Implementierung der dinierenden Philosophen mit Deadlockvermeidung durch “Ziehen mit Zurücklegen”. 1. Diese Implementierung beinhaltet drei Fehler, die zu einem fehlerhaften Verhalten führen können. Geben Sie für jeden dieser Fehler • die fehlerhafte Codestelle, • eine Erklärung, warum diese Stelle fehlerhaft ist • sowie eine fehlerbereinigte Implementierung an. Gehen Sie davon aus, dass die Sticks den Philosophen korrekt übergeben werden. 1 2 3 public class Stick { private boolean isUsed; public Stick() { isUsed = false; } 4 public synchronized void put() { notify(); } 5 6 7 8 public synchronized void take() { while (isUsed) { try { notify(); } catch (InterruptedException e) {} } isUsed = true; } 9 10 11 12 13 14 15 public synchronized boolean isUsed() { return isUsed; } 16 17 16 17 } public class Philosopher { private Stick left, right; 18 19 public Philosopher(Stick l, Stick r) { left = l; right = r; } 7 20 public void philosophize() { while (true) { System.out.println("Thinking."); 21 22 23 24 // Ziehen mit Zurücklegen right.take(); if (left.isUsed()) { right.put(); synchronized (left) { try { left.wait(); } catch (InterruptedException e) {} } } left.take(); System.out.println("Eating."); left.put(); right.put(); } 25 26 27 28 29 30 31 32 33 34 35 36 37 } 38 } 39 40 } 8