sobota 15. září 2012

Haskell - 3. kapitola

Vzory (patterns)


- můžeme definovat pro každý parametr jiné tělo fce

  1. lucky :: (Integral a) => a -> String  
  2. lucky 7 = "LUCKY NUMBER SEVEN!"  
  3. lucky x = "Sorry, you're out of luck, pal!"   

Pro číslo 7 a pro ostatní čísla jsou definovány různá těla fce.

Poznámka: tady jsem si všiml zajímavosti. První řádek specifikuje typ fce (viz předchozí kapitola). Když ho dáme pryč, typ fce bude: lucky :: (Eq a, Num a) => a -> [Char]. Nejspíš je to způsobený odvozováním typu. Když nechám jen poslední řádek s podmínkou pro x obecně, typ fce bude: lucky :: t -> [Char].

Příklad: fce pro výpočet faktoriálu pomocí vzorů (řešení str.36 dole)? A pak zkuste vypočítat faktoriál pro 100 000 a koukejte se na Matrix.

Sčítání 2 vektorů


  1. addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
  2. addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)  



Vlastní implementace fce head


  1. head' :: [a] -> a  
  2. head' [] = error "Can't call head on an empty list, dummy!"  
  3. head' (x:_) = x  

(x:_) - x zde reprezentuje první prvek z listu a _ jsou zbývající prvky - díky znaku : je zajištěno, že to musí být list. ( a ) jsou potřeba, pokud parsujeme více prvků najednou (zde parsujeme první prvek a zbytek listu)

As patterns

Někdy je potřeba získat celý výraz a i výraz rozdělený podle patternu.

  1. ghci> capital "Dracula"  
  2. "The first letter of Dracula is D"  

Fce capital vrátí větu, která obsahuje původní výraz i první písmeno z původního výrazu. Lze to udělat takto:

  1. capital :: String -> String  
  2. capital "" = "Empty string, whoops!"  
  3. capital (x:xs) = "The first letter of " ++ x:xs ++ " is " ++ [x]  


ale i hezčím způsobem:


  1. capital :: String -> String  
  2. capital "" = "Empty string, whoops!"  
  3. capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]  


Guards

- jedna z možností větvení


    1. bmiTell :: (RealFloat a) => a -> a -> String  
    2. bmiTell weight height  
    3.     | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    4.     | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    5.     | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    6.     | otherwise                   = "You're a whale, congratulations!"  
Začátek každé podmínky je označen znakem | (pipe) a pak následuje zápis podmínky. Pozor na mezeru před  |, musí tam být min. 1 mezera, jinak je to chyba.


Where

Předchozí příklad není moc DRY. To lze spravit pomocí konstrukce where:


    1. bmiTell :: (RealFloat a) => a -> a -> String  
    2. bmiTell weight height  
    3.     | bmi <= skinny = "You're underweight, you emo, you!"  
    4.     | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
    5.     | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
    6.     | otherwise     = "You're a whale, congratulations!"  
    7.     where bmi = weight / height ^ 2  
    8.           skinny = 18.5  
    9.           normal = 25.0  
    10.           fat = 30.0  


definujeme jednu vnitřní funkci a tři konstanty na konci vnější funkce. Scope těchto vnitřních fcí a konstant je pro celou vnější fci. Pozor na odsazení jejich odsazení, musejí být stejně odsazené.

Lze použít i pattern matching:

  1. initials :: String -> String -> String  
  2. initials firstname lastname = [f] ++ ". " ++ [l] ++ "."  
  3.     where (f:_) = firstname  
  4.           (l:_) = lastname   



where funkce mohou být i složitější:

  1. calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
  2. calcBmis xs = [bmi w h | (w, h) <- xs]  
  3.     where bmi weight height = weight / height ^ 2  

Zde definujeme vnější funkci calcBmis, kterou používá ve svém těle vnitřní funkci bmi. Vnitřní fce bmi není dostupná zvenku, ale jen uvnitř calcBmis.

Let


  1. cylinder :: (RealFloat a) => a -> a -> a  
  2. cylinder r h = 
  3.     let sideArea = 2 * pi * r * h  
  4.         topArea = pi * r ^2  
  5.     in  sideArea + 2 * topArea  


let na rozdíl od where prvně přiřazuje a až pak provádí funkci v bloku in. Rozdíl je též ve scope definovaných funkcí. Let není vidět mezi guardy jako u wherelet je také výraz:


  1. ghci> 4 * (let a = 9 in a + 1) + 2  
  2. 42  


Přepis funkce calcBmis do podoby s let:


  1. calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
  2. calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]  


Rozdíl ve scope, když v ghci napíšeme let s konstrukcí in či bez ní:


  1. ghci> let zoot x y z = x * y + z  
  2. ghci> zoot 3 9 2  
  3. 29  
  4. ghci> let boot x y z = x * y + in boot 3 4 2  
  5. 14  
  6. ghci> boot  
  7. <interactive>:1:0Not in scope: `boot'  



Case

  1. describeList :: [a] -> String  
  2. describeList xs = "The list is " ++ case xs of [] -> "empty."  
  3.                                                [x] -> "a singleton list."   
  4.                                                xs -> "a longer list."  

Klasika, lze tu použít i pattern matching.

Žádné komentáře:

Okomentovat