Náhodné poznámky z Haskellu

Inštalácia

%TODO

Hello World

ghci>   print "Hello World"
  • reťazce sú v úvodzovkách
  • print je funkcia s jedným parametrom
  • okolo parametrov funkcie sa nepíšu zátvorky

Zoznamy

  • základná dátová štruktúra
  • prvky musia byť rovnakých typov

Zoznamové literály

Vytvorte zoznam troch čísiel

ghci>   [1, 2, 3]

Pokúste sa vytvoriť zoznam čísla a mena

ghci>   [1, "John"]

Chyba:

No instance for (Num [Char]) arising from the literal `1'
Possible fix: add an instance declaration for (Num [Char])
In the expression: 1
In the expression: [1, "John"]
In an equation for `it': it = [1, "John"]
  • Zoznamy musia mať rovnaké prvky.
    • Ak chceme zoznam nerovnakých prvkov, môžeme využiť tice (tuples), o nich ale neskôr.

Funkcie

Vypočítajte súčet čísiel od 1 po 10

ghci>   sum [1..10]

55
  • sum je zabudovaná funkcia
    • v skutočnosti každá funkcia patrí do nejakého modulu
    • automaticky je dostupný modul Prelude

Vypočítajte súčet čísiel od 1 po 10 [Gauß style]

ghci>   (10 * (10 + 1)) / 2

Vytvorte funkciu pre súčet čísiel od 1 po n

ghci>   let sucet_po n = n * (n + 1) / 2
  • v tejto deklarácii nepoužívame dátové typy
    • Haskell ich odvodí
    • je však dobrou konvenciou deklarovať typy explicitne
  • funkcia pripomína matematický zápis
  • pokiaľ sme v REPL editore, potrebujeme dať pred funkciu let
  • funkciu voláme:

    ghci>   sucet_po 10
    

Vytvorte funkciu pre faktoriál

ghci>   let faktorial n = product [1..n]
  • využili sme matematickú definíciu 1 x 2 x 3 x … x n
  • vieme zapísať aj podľa matematického rekurzívneho predpisu, ale to nie je potrebné, ani efektívne

Vytvorte funkciu pre sínus

V externom súbore:

faktorial n = product [1..n]
sinus x n = sum $ [ ((-1)**i / faktorial(2*i + 1)) * (x ** (2*i + 1)) | i <- [0..n] ]

A potom:

ghci>   sinus (3.14 / 2) 10

0.9999996829318347

Vypočítajte korene kvadratickej rovnice

kvadr_rovnica_diskr a b c = b**2 - (4 * a * c)
kvadr_rovnica_x_1 a b c = ((-b) + sqrt d) / (2 * a) where d = kvadr_rovnica_diskr a b c
kvadr_rovnica_x_2 a b c = ((-b) - sqrt d) / (2 * a) where d = kvadr_rovnica_diskr a b c
kvadr_rovnica a b c = (kvadr_rovnica_x_1 a b c, kvadr_rovnica_x_2 a b c)

Vypočítajte rozmery ISO papiera (od A0 po A11)

ghci>   let iso (x, y) = (y / 2, x)
ghci>   take 11 $ iterate iso (841, 1189)

Alebo:

ghci>   map (\x -> (x, x * sqrt 2)) $ take 11 $ iterate iso 841

Nájdite permutácie daného zoznamu

import Data.List

-- []    -> [[]]
-- [1]   -> [[1]]
-- [1,2] -> [[1,2],[2,1]]
permutacie [] = [[]]
permutacie [x] = [[x]]
permutacie zoznam = map (\prvok -> [prvok] ++ delete prvok zoznam) zoznam
  • druhý riadok je zbytočný, je kvôli prehľadnosti
  • pozor na hraničný prípad, musíme obsiahnuť prázdny zoznam v zozname

Nájdite podmnožiny daného zoznamu

podmnoziny [] = [[]]
podmnoziny [x] = [[x]]
podmnoziny (x:xs) = podmnoziny xs ++ [ x : zvysok | zvysok <- powerset xs ]

Vygenerujte binárne postupnosti maximálnej zadanej dĺžky

nj 1 = ["0", "1"]
-- nj 2 = ["00", "01", "10", "11", "0", "1"]
nj n = (map (\x -> "0" ++ x ) $ nj (n-1) ) ++ (map (\x -> "1" ++ x ) $ nj (n-1))

Alebo:

nj 1 = ["0", "1"]
-- nj 2 = ["00", "01", "10", "11", "0", "1"]
nj n = (map (\x -> '0' : x ) $ nj (n-1) ) ++ (map (\x -> '1' : x ) $ nj (n-1))

Po eta-redukcii:

nj 1 = ["0", "1"]
-- nj 2 = ["00", "01", "10", "11", "0", "1"]
nj n = (map ('0':) $ nj (n-1) ) ++ (map ('1':) $ nj (n-1))

S where:

nj 1 = ["0", "1"]
nj n = (map ('0':) kratsi ) ++ (map ('1':) kratsi) where kratsi = nj (n-1)

S číslami:

nj 1 = [[0], [1]]
nj n = (map (0:) kratsi ) ++ (map (1:) kratsi) where kratsi = nj (n-1)

Vymažte n-tý znak z reťazca

deleteAt index str = left ++ tail right 
                       where (left, right) = splitAt index str

Na vstupe je zoznam čísiel. Vytvorte nový zoznam, kde každý prvok bude obsahovať reťazec s príslušným počtom hviezdičiek.

[2, 3, 2, 1, 5] ==> ["**","***","**","*","*****"]

Využijeme:

  • prvok prevedieme na reťazec hviezdičiek cez vlastnú funkciu

    ghci>   let toStars n = replicate n '*'
    
    • replicate je zabudovaná funkcia z Prelude
  • mapovanie cez funkciu map

    ghci>   map toStars [2, 3, 2, 1, 5]
    

Alternatívne [cez lambdu]

map (\n -> replicate n '*') [2, 3, 2, 1, 5]

Alternatívne [cez flip]

Všimnime si, že replicate berie najprv číslo a potom reťazec. Ak by sme vedeli vymeniť argumenty, mohli by sme využiť curryfikáciu / eta-redukciu

ghci>   let replicate' list n = replicate n list
ghci>   map (replicate' '*') [2, 3, 2, 1, 5]

Na takúto výmenu argumentov slúži funkcia flip, ktorá vezme binárnu funkciu a vráti funkciu so vzájomne vymenenými argumentami:

ghci>   map (flip replicate '*') [2, 3, 2, 1, 5]    

Získať i-ty prvok zo zoznamu

Toto robí funkcia !!. Vlastné riešenie:

list_get [x] index 
    | index == 0 = x
    | otherwise  = error "Index out of range"
list_get (x:xs) index  
    | index == 0 = x
    | otherwise = list_get xs (index - 1)
  • ak máme jednoprvkový zoznam, vrátime jeho nultý prvok. (Pre ostatné indexy vyhodíme chybu.)
  • ak máme viacprvkový zoznam, napr. AHOJ
    • ak chceme nultý index, vrátime hlavu (A)
    • ak chceme väčší index, napr. 2 (chceme získať O), zoberieme chvost zoznamu (HOJ) a hľadáme v ňom index o jedna menší (teda 1)

Nahraďte i-tý prvok

list_replace [x] index newElement 
    | index == 0 = [newElement]
    | otherwise  = error "Index out of range"

list_replace (x:xs) i newElement 
    | i < 0  = error "Index must be >0"
    | i == 0 = newElement : xs
    | otherwise  = x : list_replace xs (i-1) newElement
  • ak máme jednoprvkový zoznam, nahradíme jeho nultý prvok. (Pre ostatné indexy vyhodíme chybu.)
  • ak máme viacprvkový zoznam, napr. AHOJ
    • ak chceme nultý index, nahradíme hlavu novým prvkom a nalepíme na ňu pôvodný chvost
    • ak chceme väčší index, napr. 2 (nahradiť znak O), zoberieme chvost zoznamu (HOJ) a nahradíme v ňom prvok na indexe o jedna menšom (teda 1). Pred to všetko predlepíme nezmenenú pôvodnú hlavu.

Rozsahy (ranges)

Vytvorte zoznam čísiel od 1 po 10.

[1..10]

Výsledok:

[1,2,3,4,5,6,7,8,9,10]

Vytvorte zoznam párnych od 1 po 10.

[2,4..10]
  • Vieme deklarovať krok (step).

Spočítajte

Dátové typy

Bedáky s typmi

Toto funguje:

ghci>   sqrt 25
5.0

Lebo typy:

ghci>   :t 25
25 :: Num a => a
  • Konštanta 25 sa správa ako polymorfný literál
  • dá sa použiť v úlohe rozličných typov čísiel
  • napríklad:

    ghci>   :t sqrt
    sqrt :: Floating a => a -> a
    
  • napríklad pri sqrt sa vyhlási konštanta 25 za Floating, teda rodinu čísiel s desatinnou čiarkou

Referencie

  • http://pnyf.inf.elte.hu:8000/Expressions_en.xml#polymorphic-literals-and-constants
  • http://www.haskell.org/pipermail/beginners/2010-July/004542.html
  • https://stackoverflow.com/questions/19926992/haskell-ghci-why-type-of-1-is-num

Nefungujúca vec

Toto nefunguje:

ghci>   let n = 25
n :: Integer

ghci>   sqrt n

No instance for (Floating Integer) arising from a use of `sqrt'
Possible fix: add an instance declaration for (Floating Integer)
In the expression: sqrt n
In an equation for `it': it = sqrt n

Je to kvôli odvodzovaniu typov:

ghci>   :t sqrt
sqrt :: Floating a => a -> a

Funkcia sqrt berie číslo z rodiny Floating a vracia číslo z rodiny Floating. Dátový typ Integer (celé číslo) však nepatrí do rodiny čísiel s plávajúcou čiarkou (logicky, nie? a keď nie, tak viď obrázok na http://bit.ly/1gbrnOm) a preto nebudú sedieť dátové typy, odkiaľ prýšti hláška “neexistuje inštancia pre celé číslo s desatinnou čiarkou”.

Alebo tiež

ghci>   let f :: Int -> Int; f x = 3.14 * x

No instance for (Fractional Int) arising from the literal `3.14'
Possible fix: add an instance declaration for (Fractional Int)
In the first argument of `(*)', namely `3.14'
In the expression: 3.14 * x
In an equation for `f': f x = 3.14 * x
  • toto je blbosť už z matematického zápisu: celé číslo vynásobené desatinným nikdy nedá celé číslo
  • v Haskelli:
    • x musí byť Int (celé číslo)
    • ale neexistuje definované násobenie medzi číslom s pevnou desatinnou čiarkou (Fractional) a celým číslom (Int), preto nesedia typy

Ak opravíme výsledok na:

let f :: Int -> Double; f x = 3.14 * x

Couldn't match expected type `Double' with actual type `Int'
In the second argument of `(*)', namely `x'
In the expression: 3.14 * x
In an equation for `f': f x = 3.14 * x

Funkcia má rozpor: x je deklarované ako Int, ale po násobení s konštantou 3.14, ktorá je z rodiny Fractional, nevznikne automaticky Double.

Môžeme to opraviť na:

ghci>   let f :: Int -> Double; f x = 3.14 * fromIntegral x

A potom zavolať:

ghci>   f 3.14

No instance for (Fractional Int) arising from the literal `3.14'
Possible fix: add an instance declaration for (Fractional Int)
In the first argument of `f', namely `3.14'
In the expression: f 3.14
In an equation for `it': it = f 3.14

Funkcia f nepodporuje celé čísla: na vstupe čaká Int, ale dostala 3.14 z rodiny Fractional, čo nesedí.

Riešenie:

let f :: Double -> Double; f x = 3.14 * x

Iná nefungujúca vec

Vypočítajte priemer známok

Prvý pokus:

ghci>   let znamky = [1, 1, 2, 2, 2, 2, 4, 3]
ghci>   sum znamky / length znamky

Ale:

Couldn't match expected type `Integer' with actual type `Int'
In the return type of a call of `length'
In the second argument of `(/)', namely `length znamky'
In the expression: sum znamky / length znamky

Lebo:

ghci>   :t sum znamky

sum znamky :: Integer

A:

ghci>   :t length

length :: [a] -> Int

Inak povedané, chceme deliť Integer typom Int, ale to sú nezávislé a odlišné dátové typy.

Skúsme previesť druhý parameter na Integer. Funkcia fromIntegral prevedie číslo na “taký dátový typ, ako treba”.

ghci>   :t fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b

Skúsme:

sum znamky / fromIntegral (length znamky)

No instance for (Fractional Integer) arising from a use of `/'
Possible fix: add an instance declaration for (Fractional Integer)
In the expression: sum znamky / fromIntegral (length znamky)
In an equation for `it':
    it = sum znamky / fromIntegral (length znamky)

Ďalší problém! Operátor delenia / nefunguje na celé čísla!

Riešenie:

fromIntegral(sum znamky) / (fromIntegral(length znamky))

Skúsme to cez funkciu:

ghci>    let priemer znamky = fromIntegral(sum znamky) / fromIntegral(length znamky)

priemer :: (Fractional a, Integral a1) => [a1] -> a

Funkcia má deklaráciu:

  • na vstupe zoznam, ktorého prvky sú z rodiny Integral
  • na výstupe hodnota z rodiny Fractional

Inak povedané, berie celé čísla a vracia číslo v pevnej desatinnej čiarke.

Potom ju radostne voláme:

ghci>   priemer znamky
2.125

Ak by sme funkciu deklarovali ako:

ghci>   let priemer' znamky = sum znamky / fromIntegral(length znamky)

priemer' :: Fractional a => [a] -> a

Vidíme, že Haskell odvodí deklaráciu “na vstupe je zoznam desatinných čísiel a na výstupe je jedno desatinné číslo”.

Samozrejme, potom toto nefunguje:

ghci>   priemer' znamky

No instance for (Fractional Integer) arising from a use of priemer'
Possible fix: add an instance declaration for (Fractional Integer)
In the expression: priemer' znamky
In an equation for `it': it = priemer' znamky

Premenná znamky je totiž zoznam Integerov a nesedia typy.

Vyriešiť to môžeme konverziou zoznamu:

ghci>   priemer' $ map (\x -> fromIntegral x) znamky

Alebo redeklaráciu premennej:

ghci>   let znamky = [1, 1, 2, 2, 2, 2, 4, 3] :: [Double]

znamky :: [Double]

Bedáky s násobením

ghci>   5 * 5.0
25.0
it :: Double

Ale:

ghci>   let pat = 5
pat :: Integer

A:

ghci>   let patCelychNula = 5.0
patCelychNula :: Double

A potom:

ghci>   pat * patCelychNula

<interactive>:61:7:
    Couldn't match expected type `Integer' with actual type `Double'
    In the second argument of `(*)', namely `patCelychNula'
    In the expression: pat * patCelychNula
    In an equation for `it': it = pat * patCelychNula

Chceme násobiť Integer číslom typu Double, čo nie je definované. Lebo:

ghci>   :t (*)

(*) :: Num a => a -> a -> a

Čísla musia byť rovnakých typov.

Haskell totiž nemá implicitné typové konverzie (coercion) pre čísla. Na rozdiel od Javy, kde 5 * 5.0 (teda int krát double) funguje vďaka koercii intu na double, musíme prevod urobiť explicitne.

Napríklad:

ghci>   fromIntegral(pat) * patCelychNula

25.0
it :: Double

Zdroj: http://www.haskell.org/haskellwiki/Converting_numbers

Veľké programy

-- prog-with-main.hs
main = print "Hello"

Spustíme:

d:\work\haskell>runhaskell prog-with-main

Program využíva I/O akcie (čo sú monády, teda výpočty pozostávajúce z krokov, ktoré majú vedľajšie efekty, t. j. menia stav vonkajšieho sveta).

Zdroje
  • https://www.fpcomplete.com/school/starting-with-haskell/basics-of-haskell/the-tao-of-monad

Vypíšte 10krát “Budem si robit DU”

-- domace-ulohy.hs
main = do
    mapM (\n -> print "Budem si robit D. U. z Haskellu") [1..10]
  • mapovaním vykonáme nad každým prvkom zoznamu funkciu “vytlač”
  • funkcia “vytlač” nie je bežná rýdza funkcia, ale I/O akcia
  • na mapovanie monád, resp. I/O akcií musíme použiť špeciálnu funkciu mapM (M ako monáda).

Výsledok:

d:\work\haskell>runhaskell prog-with-main
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
"Budem si robit D. U. z Haskellu"
[(),(),(),(),(),(),(),(),(),()]

GHCI vždy vyhodnotí celú I/O akciu main (lebo aj vnútro v do bloku je zložená I/O akcia). Ak chceme ignorovať výsledok, použime radšej mapM_:

-- domace-ulohy.hs
main = do
    mapM_ (\n -> print "Budem si robit D. U. z Haskellu") [1..10]

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *