Direkt zum Inhalt | Direkt zur Navigation

Sektionen
Benutzerspezifische Werkzeuge

Seminar 2 - Teil II

Einfache Datentypen - Tim Richter

seminar02_2.lhs — LHS source code, 6Kb

Dateiinhalt

----------------------------------------------------------------------
--
--  Seminar Theoretische Informatik                         \ \
--  "Funktionale Programmierung mit Haskell"                 \ \ ---
--  Universität Potsdam SS 2014                              / /\ --
--  Tim Richter / Mario Frank                               / /  \
--
--  2: Einfache Typen II
----------------------------------------------------------------------

> module Seminar02_2 where

----------------------------------------------------------------------
-- Typklassen Eq und Ord
----------------------------------------------------------------------

"=" im Gleichheitsschliessen ist mathematische Gleichheitsnotation,
insbesondere ist "alles sich selbst gleich", Funktionen sind gleich,
wenn sie auf allen Werten gleich sind, usw.

Oft benötigt man aber einen Gleichheitstest auf einem Typen, d.h. eine 
(totale, d.h. überall terminierende) Funktion

< (==) :: a -> a -> Bool

Einen solchen Test kann man aber nicht für alle Typen definieren (z.B.
nicht für Funktionen auf Nat).

In Haskell gibt es eine Typklasse aller Typen, für die ein Gleichheits-
test implementiert ist.

< class Eq a where
<   (==),(/=) :: a -> a -> Bool

(==) "sollte" natürlich eine Äquivalenzrelation definieren,
aber es könnte auch x == y = False definiert werden.

Auch selbstdefinierte Typen können zu "instances" von Typklassen gemacht
werden, z.B.

> data AN = MarcUwe | Känguru | Krapottke | FW
>
> instance Eq AN where
>   x == y = True

Es genügt, (==) oder (/=) zu definieren, für die jeweils andere Funktion
gibt es die Defaultdefinitionen

< x /= y = not ( x == y )
< x == y = not ( x /= y )

Wieder gibt es die "semantische" oder "moralische" Verpflichtung,
  x == y  =  not ( x /= y )
auch dann zu haben, wenn man sowohl (==) als auch (/=) implementiert,
aber Haskell kann dies nicht erzwingen.

Es gibt darüberhinaus die Typklasse Ord

< class Eq a => Ord a where
<  (<),(<=),(>=),(>) :: a -> a -> Bool
<  compare :: a -> a -> Ordering

Hier ist

< data Ordering = LT | EQ | GT

und Definition von compare reicht wieder aus.

> instance Ord AN where
>   compare x y = EQ

Für viele Typen kann Haskell automatisch Typklasseninstanzen
ableiten:

> data Triangletype = Failure | Isosceles | Equilateral | Scalene 
>     deriving (Eq, Ord, Show)

Die meisten builtin Types (z.B. Int) sind ohnehin in Eq, Ord, ...

(==),(<=) etc. sind nützlich, um Funktionen mit Hilfe von sogenannten
"guards" zu definieren:

> analyse :: (Int,Int,Int) -> Triangletype
> analyse (x,y,z) | x + y <= z           = Failure
>                 | x == z               = Equilateral
>                 | (x == y) || (y == z) = Isosceles
>                 | otherwise            = Scalene

Es gibt hier nur ein pattern, alle Tripel aus (Int,Int,Int) (bis auf 
undefined, (undefined,2,3) usw.) matchen auf dieses pattern.

Die "guards" sind Boolsche Ausdrücke, werden wie pattern von oben nach 
unten ausgewertet, falls True, wird zur rechten Seite von "=" reduziert.
otherwise (= True) am Ende ist der default Fall.

----------------------------------------------------------------------
-- Enumerations
----------------------------------------------------------------------

< class Enum a where
<   succ :: a -> a
<   pred :: a -> a
<   toEnum :: Int -> a
<   fromEnum :: a -> Int
<   ...

Typen, die "durchgezählt" werden können. Ausreichend zur Instanziierung
sind die Definitionen von toEnum und fromEnum. Für Charaktere kennt man
in vielen Programmiersprachen Funktionen ord und chr, die zwischen einem
Zeichen und seinem ASCII-Code übersetzen. In Haskell sind diese nicht
vordefiniert, Char ist aber in Enum und fromEnum und toEnum machen genau
die Arbeit von ord und chr

> ord :: Char -> Int
> ord = fromEnum
>
> chr :: Int -> Char
> chr = toEnum

Man erwartet natürlich von Implementationen, dass sie

  toEnum ( fromEnum x) = x

erfüllen. Dieses "Gesetz" kann Haskell aber nicht erzwingen,
der Programmierer muss selbst aufpassen.

Auch hier gibt es "deriving" für viele Typen, z.B.

> data Wochentag = Mo | Di | Mi | Do | Fr | Sa | So
>   deriving (Eq, Ord, Enum, Bounded, Show)

Bounded ist eine weitere Typklasse, die

< minBound, maxBound :: a

definiert.

Übung: Definieren Sie Funktionen unter Benutzung von fromEnum und/oder
toEnum

< arbeitstag, wochenende :: Wochentag -> Bool

----------------------------------------------------------------------
-- Tupel
----------------------------------------------------------------------

Wir hatten Tupel schon benutzt ((Int,Int,Int)) usw. (,) ist syntactic 
sugar, aber ansonsten ist (a,b) ganz analog zu

> data Pair a b = MkPair a b

Zwei Typparameter, a und b, sie können verschieden sein!
Der Datentyp hat den Datenkonstruktor

< MkPair :: a -> b -> Pair a b

Typkonstruktoren und Datenkontruktoren dürfen denselben Namen benutzen!
Aber: zwei Datenkonstruktoren (auch verschiedener Typen) dürfen nicht 
gleich heissen!

Standardfunktionen sind die Projektionen

< fst :: (a,b) -> a
< fst (x,y) = x
<
< snd :: (a,b) -> b
< snd (x,y) = y

Da Konstruktoren immer non-strict sind, sind 
  (undefined,undefined) und undefined verschieden

Übung: Schreiben Sie eine Funktion

< test :: (Int,Int) -> Bool

die sich auf diesen beiden Werten unterschiedlich verhält!

Für das Gleichheitsschliessen und die "algebra of programming" wichtige 
higher order functions für (,) sind:

> pair :: (a->b,a->c) -> a -> (b,c)
> pair (f,g) x = (f x, g x)

> cross :: (a->b,c->d) -> (a,c) -> (b,d)
> cross (f,g) = pair (f . fst, g . snd)

Leicht beweist man folgende Gesetze:

 1)  fst . pair (f, g)        =  f
 2)  snd . pair (f, g)        =  g
 3)  pair (f, g) . h          =  pair (f.h,g.h)
 4)  cross (f,g) . pair (h,k) =  pair (f.h,g.k)

Zur Illustration der Beweis für 4) unter Benutzung von 1),2) und 3)

  cross (f,g) . pair (h,k)
  =  {def cross}
  pair (f.fst, g.snd) . pair (h,k)
  =  { 3) }
  pair (f.fst.pair(h,k), g.snd.pair(h,k))
  =  { 1) und 2) }
  pair (f.h,g.k)

----------------------------------------------------------------------
-- Either
----------------------------------------------------------------------

< data Either a b = Left a | Right b

Hier haben wir zwei Konstruktoren:

<   Left   :: a -> Either a b
<   Right  :: b -> Either a b

Wichtige higher order functions:

> scase :: (a->c,b->c) -> Either a b -> c
> scase (f,g) (Left x)   = f x
> scase (f,g) (Right y)  = g y

> plus :: (a->b,c->d) -> Either a c -> Either b d
> plus (f,g) = scase (Left . f, Right . g)

Either a b ist "dual" zu (a,b) und es gelten für scase und plus
analoge Gesetze wie für cross und pair:

i)   scase (f,g) . Left      = f
ii)  scase (f,g) . Right     = g
iii) h . scase (f,g)         = scase (h.f,h.g)
iv)  scase (f,g) . plus(h,k) = scase (f.h,g.k)

Übung: Vergleichen Sie mit 1)-4) oben! Beweisen Sie iv) unter
Benutzung von i)-iii)!

Bemerkung: In Birds Buch heisst die Funktion scase "case". Das 
kollidiert mit dem ghc-Schlüsselwort "case". 













Artikelaktionen
Auf einen Blick
Lehrform Seminar
Empfohlen ab FS 3
Voraussetzungen Grundkenntnisse der Informatik aus den ersten 4 Semestern, vor allen aus den Bereichen Theoretische Informatik und Logik. Teilnahme an der Veranstaltung "Automatisierte Logik und Programmierung" ist vorteilhaft, aber nicht notwendig.
Benotet Ja
Punkte gesamt 3
davon praktisch 3
Sprache deutsch/englisch
Fremdhörer zugelassen? Nein
Teilgebiete Theoretische Informatik(2000), Praktische Informatik(3000), Wahlfrei(7000)
Studiengang Bachelor, Master
Belegung via PULS