Prof. Dr. Manfred Schmidt-Schauß Künstliche Intelligenz/Softwaretechnologie Fachbereich Biologie und Informatik / Institut für Informatik Johann Wolfgang Goethe-Universität Frankfurt am Main Praktikum Praktische Informatik Sommersemester 2002 Aufgabenblatt Nr. 5 Lösungen bis Mittwoch, 19. Juni 2002 spätestens 18:00 Aufgabe 1: Stellenwertsysteme Nicht nur in der maschinennahen Programmierung werden häufig Stellenwertsysteme zu einer von 10 abweichenden Basis verwendet. In dieser Aufgabe sollen Sie Datentypen und Typklassen in Haskell entwickeln, mit denen Zahlen verschiedener Stellenwertsysteme ineinander umgerechnet werden können. Betrachten Sie dazu die folgende Klassendefinition: > class Digit d where > digitToInteger :: d -> Integer > integerToDigit :: Integer -> Maybe d > > > base :: d -> Integer -- falls der Integer überhaupt -- in einer Stelle dargestellt -- werden kann Die beiden Methoden digitToInteger und integerToDigit erinnern dabei ein wenig an fromInteger aus der Klasse Num und toInteger aus der Klasse Integral; entscheidend ist hier aber die zusätzliche Methode base zum Ermitteln der Basis. a) Implementieren Sie zunächst zwei Datentypen BinDigit und HexDigit, um Ziffern des Binär- bzw. Hexadezimalsystems repräsentieren zu können, und machen Sie diese auf sinnvolle Weise zu Instanzen der Typklassen Digit und Show, wobei Sie auch die Methode showList definieren sollten. b) Überlegen Sie sich eine geeignete Datenstruktur für die Darstellung von Zahlen1 in einem Stellenwertsystem und beschreiben Sie präzise deren Semantik, d.h. wo sich welche Stelle wiederfindet. Definieren Sie dann mit Hilfe dieser Datenstruktur einen Typen2 ValuePlaceSystem d für ein Stellenwertsystem über dem Ziffer-Typ d. c) Programmieren Sie nun polymorphe Funktionen numberToInteger und integerToNumber mit den Typen > numberToInteger :: Digit d => ValuePlaceSystem d -> Integer > integerToNumber :: Digit d => d -> Integer -> ValuePlaceSystem d um eine Zahl in einem Stellenwertsystem zur Basis d in einen Integer umzurechnen und umgekehrt. Das erste Argument von integerToNumber dient zur Ermittlung der Basis. 1 2 Diese sind ja eine eine Ansammlung von Ziffern Auch ein Typsynonym mittels type oder ein newtype wären hier möglich. 1 Aufgabe 2: Stackmaschine Mikroprozessoren gehen allgemein nach dem Schema Befehl holen und abarbeiten vor. Generell benötigt man dazu einen Arbeitsspeicher3 , in dem neben den Daten, auf denen operiert werden soll, auch die Befehle, die abzuarbeiten sind, abgelegt werden4 . Damit die Abarbeitung eines Programmes fortschreitet, wird ein Befehlszähler immer auf die Adresse des als nächstes zu holenden Befehls gesetzt. Der Befehlszähler ist im allgemeinen ein Register, d.h. eine im Prozessor eingebaute Speicherstelle. Je nachdem, wie viele solcher Register ein Prozessor besitzt und wozu diese benutzt werden können, unterscheidet man grob5 in die folgenden drei Klassen von externen6 Architekturen: Registersatzmaschine: Besitzt einen relativ großen Satz (16 und mehr) gleichberechtigter Universalregister, d.h. jeder Befehl kann mit jedem Register oder auch mehreren benutzt werden, aber eben auch nur mit Registern. Im Speicher stehende Werte müssen über spezielle Load–Befehle in Register geladen bzw. über Store–Befehle in den Speicher zurückgeschrieben werden. Stackmaschine: Stellt dem Programmierer keine Register zur Verfügung, sondern bezieht alle Befehle implizit auf einen Stack, einen speziellen Bereich des RAM. Akkumulatormaschine: Kennt nur ein oder zwei Register, auf die sich alle Befehle beziehen7 , und darüberhinaus nur noch wenige weitere, die anderen speziellen Zwecken dienen wie z.B. Adressierung. Zusammen mit dem nächsten Aufgabenblatt soll ein Simulator für eine einfache Stackmaschine entwickelt werden. Hier gehen wir erst einmal von dem in Tabelle 1 aufgeführten Befehlssatz aus. Alle dort erwähnten Zahlen sollen vom Typ Integer sein. Achten Sie auf einen modularen Entwurf, indem Sie schrittweise u.a. nach folgenden Punkten vorgehen: a) Definieren Sie zuerst — jeweils in separaten Moduln — abstrakte Datentypen Stack e für einen Stack von Elementen des Typs e und Memory a e, um das RAM zu modellieren. Dabei soll e der Typ der Elemente sein und der Typ a die Adressen repräsentieren. b) Spezifizieren Sie einen Typ State a für den Zustand der Maschine, wobei a den Typ für die Adressen vorgibt. Verwenden Sie einen Record-Typen8 , damit der Zustand leicht um weitere Komponenten erweitert werden kann. Sie können den Aufbau der Stackmaschine einfach halten, indem Sie den Stack nicht als ein Teil des RAM betrachten sondern separat verwalten. Auch die Instruktionen müssen nicht im Speicher ablegt werden. 3 4 5 6 7 8 Random Access Memory sogenannte Von–Neumann Architektur Natürlich kommen in der Praxis durchweg Mischformen vor. im Gegensatz zum internen Aufbau eines Prozessors u.U. implizit Im Haskell-Report werden diese Datatypes with Field Labels“ genannt. ” 2 Befehl push pop dbl load Parameter zahl stor addr addr add, sub, mul, div Bedeutung die Konstante zahl wird auf den Stack gelegt das oberste Stackelement wird gelöscht das oberste Stackelement wird erneut auf den Stack gelegt eine Zahl wird von der Speicheradresse addr gelesen und auf den Stack gelegt das oberste Element wird vom Stack genommen und im Speicher unter addr abgelegt Die beiden obersten Elemente werden vom Stack genommen und das Ergebnis der entsprechenden Rechenoperation auf dem Stack abgelegt Tabelle 1: Befehlssatz der Stackmaschine c) Implementieren Sie die Instruktionen aus Tabelle 1 als Funktionen auf dem Zustand der Maschine und zwar derart, daß pop, dbl, add, sub, mul und div genauso wie die partiellen Anwendungen von push, load und stor auf jeweils ein Argument sämtlich vom Typ Instruction a sind, welcher polymorph über dem Adresstyp a wie folgt definiert sei: > type Instruction a = State a -> State a Hinweis: Identifizieren Sie hierbei wiederkehrende Operationen und abstrahieren Sie mittels Funktionen höherer Ordnung. d) Programmieren Sie eine Funktion run :: State a -> State a, die die Maschine auf dem angegebenen Startzustand laufen läßt, bis keine Befehle mehr abzuarbeiten sind. Testen Sie Ihre Implementierung — startend jeweils mit leerem Stack und leerem Heap — insbesondere mittels der folgenden drei Instruktionsfolgen: push push push add dbl stor mul dbl stor 4 3 2 "3und2" "undMal4" push stor push stor push stor load load dbl mul add 3 17 0 13 0 11 11 0 11 push 17 dbl stor 1 load 17 add