Haskell

Eine funktionale Programmiersprache

filter :: (a -> Bool) -> [a] -> [a]
filter _ []        = []
filter pred (x:xs)
  | pred x         = x : filter pred xs
  | otherwise      = filter pred xs

Quick-Check

Paradigmen

  • funktional
    • keine Nebenwirkungen, keine Invarianten
    • z.B. Scala, SQL, JavaScript, (C#), XSLT, uvm.
  • nicht-strikt: lazy, Auswertung nach Bedarf
  • deklarativ: Beschreibung des Was, nicht des Wie

Typ-System

  • statisch
  • stark
  • inferiert

Weitere Infos

  • Compiler: Glaskow Haskell Compiler (GHC) u.a.
  • entwickelt Ende der 80er, u.a. von Simon Peyton Jones
  • Funktionen höherer Ordnung (Closures, Lambda-Kalkül)
  • algebraische Datentypen
  • Currying
  • Pattern matching

Die Syntax

Case-Sensitive

  • beginnend mit Großbuchstaben
    • Typen, Konstruktoren, Typ-Klassen
    • String, Int, LT, Num
  • beginnend mit Kleinbuchstaben
    • Bezeichner, Typ-Variablen, Funktionen, Parameter
    • fun x = x * x
    • sort :: Ord a => [a] -> [a]

Struktur per Einrückung

  • Indentation-based, vgl. Python u. CoffeeScript
  • vorzugsweise (mindestens 2) Leerzeichen pro Ebene

Kommentare

  • Einzeilige Kommentare: "-- "
  • Mehrzeilige Kommentare: "{- -}"
  • auch geschachtelt möglich
    • "{- Kommentar-Block {- innerer Block -} wieder äußerer Block -}"

Typ-System

  • Compiler ist strikter als bekannte andere
  • zwingt Programmierer zum genauen Nachdenken
  • wenns compiliert, läufts (meist) auch fehlerfrei

Basis-Datentypen

  • let c = 'a'          -- Typ Char
  • let s = "abc"        -- Typ String, bzw. [Char]
  • let i = 0            -- Typ Int
  • let i = 0 :: Integer -- Typ Integer
  • let d = 0.0          -- Typ Double
  • let d = 0 :: Double  -- Typ Double
  • let b = True         -- Typ Bool, Gegenteil: False
  • let l = [1,2,3,4]    -- Typ [Int]
  • let l = [1..10]      -- Kurzform einer Liste
  • let infinite = [1..] -- unendliche Liste (lazy)
  • let t = (1,"abc")    -- Typ (Int, String)

Listen

  • Listen sind keine Arrays!
  • Element an den Anfang:
    • Der Listen-Konstruktor
    • let newList = newElement : list
  • Element an das Ende:
    • Die append-Funktion
    • let new List = list ++ [newElement]
  • Prepending ist besser als Appending

Listen

  • Gestalt von Listen ähnelt der von LinkedLists
  • List-Comprehensions:
    • let xs = [x*x | x <- [1..10]]
  • Pattern-Matching mit Listen:
    • filter :: (a -> Bool) -> [a] -> [a]
      filter _ []        = []
      filter pred (x:xs)
        | pred x         = x : filter pred xs
        | otherwise      = filter pred xs
                  

Eigene Datentypen

data Day =
    Monday
    | Tuesdays
    | Wednesdays
    | Thursday
    | Friday
    | Saturday
    | Sunday
data Person =
    Person String Int

Typ-Synonyme

type Name = String
type Age = Int
data Person = Person Name Age
let person = Person "SDK" 30

Wie kommen wir an die Eigenschaften heran?

Accessoren

getName (Person n _) = n
getAge (Person _ a) = a

Geht das noch besser/einfacher?

Records

data Person = Person
  { name :: String
  , age :: Int
  }
let p = Person "SDK" 30
let p = Person
          { name = "SDK"
          , age = 30
          }

Accessoren:

name :: Person -> Name
age :: Person -> Age

Records

Vorteile:

  • automatische Accessoren
  • extending: (ähnlich jQuery)
let newPerson =
    p { name = "Flo" }

Nachteil:

  • nicht-intialisierte Felder
  • das geht nicht:
data Person =
    Person
      { name :: Name
      , age :: Age
      }

data Dog =
    Dog
      { name :: Name
      , age :: Age
      }

Lösung:

"OverloadedRecordFields" (Language-Extension)

Wrapper-Typen

-- wird vom Compiler in Int umgewandelt
newtype Natural = MkNatural Int

Datentypen sind lazy per default, können aber mit dem strictness-flag ("!") als non-lazy bzw. strict deklariert werden. Beispiel:

data RealFloat a => Complex a = !a :+ !a

Strictness in Funktionen mit "seq" oder "!$".

Funktionen

  • Typ-Signatur:
    • polymorph, generisch
    • nimmt Ausdruck, gibt Ausdruck zurück
  • einschränkend:
    • Typ "a" muss eine Instanz der Typ-Klasse "Ord" sein (muss also sortierbar sein)
fun :: a -> b
compare :: Ord a => a -> a -> Ordering

Funktionen

Klassische Funktionen:

mult (a,b) = a*b

Haskell-Funktionen:

mult a b = a*b
multBy2 = mult 2
-- oder:
multBy2 = (*2)

Currying:

Lambda-Ausdrücke:

mult = \a b -> a*b

map2 = map (\a -> a * 2)

-- kürzer:
map2 = map (*2)

Operatoren

List-Concatanation:

infixr 5  ++
(++) :: [a] -> [a] -> [a]
(++) []     ys = ys
(++) (x:xs) ys = x : xs ++ ys

Function Composition:

infixr 9  .
(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

Operatoren

  • infixe Funktionen
  • präfixe Anwendung: 
  • normale Funktion infix:
  • Rang und Assoziativität:
  • je höher n, desto eher wird der Operator ausgeführt
  • infixr: Rechts-Assoziativität
  • infixl: Links-Assoziativität
let a = (+) 2 3
let a = 2 `mult` 3
infix[l/r] n operator

Beispiel:

infixl 7  *
infixl 6  +, -
infixr 0  $

Pattern Matching

contrived :: ([a], Char, (Int, Float), String, Bool) -> Bool
contrived    ([],  'b',  (1,   2.0),   "hi",   True) = False

As-Pattern:

f (x:xs)        = x:x:xs
-- geht kürzer
f s@(x:xs)      = x:s           -- s == x:xs

fib@(1:tfib)    = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]

Wild-Cards:

head (x:_)             = x
tail (_:xs)            = xs

Guards:

Case Expressions:

sign x |  x >  0        =   1
       |  x == 0        =   0
       |  x <  0        =  -1
let s = "1"
let default n = 0
let n = case reads s of
          [(n,_)] -> n
          _       -> 0

Let-Ausdrücke

checkPrefix s con action next = 
    let (pre, post) = T.splitAt (T.length s) con
    in  if pre == s then action post else next

Where-Ausdrücke

f x y  |  y > z           =  ...
       |  y == z          =  ...
       |  y < z           =  ...
     where z = x*x

Typ-Klassen

  • entsprechen in etwa den Interfaces im OOP oder virtual Functions in C++
  • Typen werden als Instanzen einer Typ-Klasse definiert
  • Intanziieren funktioniert per manuellem Überladen
  • Bestimmte Basis-Typ-Klassen können automatisch "derived" werden
derive (Eq, Read, Show)

Input/Output (IO)

  • IO ist monadisch
  • IO ist typsicher
getChar     :: IO Char
readFile    :: FilePath -> IO Char
-- type FilePath = String
putChar     :: Char -> IO ()   -- () ~= void
putStrLn    :: String -> IO ()
writeFile   :: FilePath -> String -> IO ()

Von IO:

Nach IO:

Die main-Funktion

main :: IO ()
main = do
  c <- getChar
  if c == 'y'
    then return ()
    else main
  • ist der Einstiegspunkt jedes Haskell-Programms
public static void main( String[] args )

Vgl:

Actions

  • Funktionen mit IO im Rückgabewert sind IO-Actions
  • do-Notation kann angewandt werden
  • "syntactiv sugar" für die Arbeit mit Monaden
  • reine Funktionen können keine IO-Actions enthalten
  • Ausnahme: unsafe Implementierungen wie Debug.Trace

Do-Notation

do { a <- f ; m } ≡ f >>= \a -> do { m }
do { f ; m } ≡ f >> do { m }
do { m } ≡ m
do {
  a <- f ;
  b <- g ;
  c <- h ;
  return (a, b, c)
}

f >>= \a ->
  g >>= \b ->
    h >>= \c ->
      return (a, b, c)

Do-Notation

(>>=) :: Monad m => m a -> (a -> m b) -> m b

(>>) :: Monad m => m a -> m b -> m b

Sequencing

putStr :: String -> IO ()
putStr = mapM_ putChar
putStr' = foldr ((>>) . putChar) (return ())

putStrLn' s = putStr s >> putChar '\n'

Zeit für ein wenig Real World Code!

Quo Vadis?

Haskell (PLUSPOL interactive)

By Stefan Von Der Krone

Haskell (PLUSPOL interactive)

  • 459