Cvičení jsme začali rozvičkou, psali jsme ve dvojicích následující funkce:
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry = _
curry :: ((a, b) -> c) -> a -> b -> c
curry = _
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith = _Nejprve se o implementaci pokuste sami, níže jsem rozepsal postup, který jsme použili na cvičeních:
- Všimneme si, že tahle funkce má dva argumenty, tedy je musíme převzít:
uncurry = \f -> \ab -> _f má typ a -> b -> c
ab má typ (a, b)
Na to přijdeme buď tím, že se podíváme na sekci "Relevant bindings" v "Typed hole" erroru,
nebo najetím myši v IDE. (Nebo rozmyšlením dle typu funkce...)
- Ze cvičení víme, že
foo = \x -> Eje to samé jakofoo x = E(syntaktický cukr):
uncurry f ab = _- Nyní máme vytvořit hodnotu typu
c. Jediný způsob jak ji vytvořit je pomocí funkcef, musíme ji tedy použít:
uncurry f ab = f firstArg secondArg
where
firstArg = _
secondArg = _firstArgmusí být něco typuaasecondArgmá být něco typub. Jediný způsob jak něco takového získat je zab, který má typ(a, b), tj. dvojice typůaab. Musíme tedy rozbalitabpomocí pattern matchování:
uncurry f (x, y) = f firstArg secondArg
where
firstArg = _
secondArg = _x je typu a, y je typu b
- Nyní
firstArgmá být něco typuaa takovou věc máme --x:
uncurry f (x, y) = f firstArg secondArg
where
firstArg = x
secondArg = _- Podobně pro
secondArgay:
uncurry f (x, y) = f firstArg secondArg
where
firstArg = x
secondArg = y- Víme, že v Haskellu můžeme cokoliv inline-ovat, takže inlinujeme
firstArgasecondArg(hodily se nám při vytváření, ale teď už nejsou k ničemu):
uncurry f (x, y) = f x yA máme hotovo! :)
Analogicky k předchozí :)
curry f x y = f (x, y)zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith = _- Nejprve převezmeme argumenty
f :: (a -> b -> c),xs :: [a],ys :: [b].
zipWith f xs ys = _- Když najedeme na typed hole (tj. na
_), Haskell říká, že tam chce dostat něco typu[c]. Mohli bychom vrátit něco jako prázdný seznam, ale to není úplně košér (rozumné funkce nezahazují svůj vstup).
Všimneme si, že když bychom nějak dostali jednu věc typu a a jednu věc typu b,
tak pomocí funkce f vyrobíme jednu věc typu c.
- Použijeme pattern matching na
xs:
zipWith f [] ys = _
zipWith f (x:xs) ys = _Co vůbec můžeme vrátit když je první seznam prázdný? Všimněme si, že jediná rozumná věc je prázdný seznam!
zipWith f [] ys = []
zipWith f (x:xs) ys = _- Použijeme pattern matching na
ysv nedořešeném (druhém) případě:
zipWith f [] ys = []
zipWith f (x:xs) [] = _
zipWith f (x:xs) (y:ys) = _Opět, dle stejného argumentu jako výše nemáme jak vyrobit cokoliv typu c.
Takže jediný seznam věcí s typem c (tj. [c]) je prázdný seznam.
zipWith f [] ys = []
zipWith f (x:xs) [] = []
zipWith f (xs:xs) (y:ys) = _- Trochu po sobě uklidíme a zapodtržítkujeme nepoužívané argumenty:
zipWith _ [] _ = []
zipWith _ _ [] = []
zipWith f (x:xs) (y:ys) = _- Zbývá nám vyřešit jediný případ. Zase bychom mohli vrátit prázdný seznam, ale v tu chvíli bychom nepoužili žádný argument v celé funkci, což asi není to co jsme chtěli.
Máme ale f :: (a -> b -> c), x :: a, xs :: [a], y :: b, ys :: [b].
Pomocí f a b jsme schopni vytvořit něco typu c a označíme to jako z.
zipWith f (x:xs) (y:ys) = _
where
z = f x y- Jak to využijeme? Mohli bychom typed hole (tj.
_) nahradit něčím jako[ z ], ale to asi není úplně fajn, protože v tu chvíli jsme zahodili celý zbytek vstupních seznamů (xsays).
Vrátíme tedy z : zs, kde zs :: [c] ještě nevíme...
zipWith f (x:xs) (y:ys) = z : zs
where
z = f x y
zs = _- Nyní chceme vytvořit
zstypu[c]. Jak to uděláme? Mohli bychom zase pattern matchovat naxsaysa aplikovat na jeden, ale pak bychom měli nějak hodně případů.
Všimneme si ale, že tohle je skvělé místo kde použít rekurzi!
zs = zipWith f xs ys- Zase slušně inlinujeme pomocné definice (jsou dostatečně jednoduché):
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ysDohromady tedy dostáváme:
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith _ [] _ = []
zipWith _ _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ysTahle funkce se chová jako map, ale bere dva seznamy a spojuje je po prvcích :)
- Používejte Typed Holes -- pokud nevíte co chcete napsat, plácněte tam
_a nechte si od GHC/IDE pomoct! :) - Řiďte se pomocí typů, je to taková logická skládačka, ve které je docela těžké selhat :D
- Pište i své funkce co nejobecněji to jde. Všimněte si, že jsme se často spoléhali na argumenty jako "je jen jeden způsob jak vytvořit něco typu
c". Takové argumenty už ale neplatí proInt. Například funkce typua -> aje jen jedna, ale funkcíInt -> Intje(2^32)^(2^32), což je fakt hodně. - Naučte se používat Hoogle -- Haskellový vyhledávač, který umí najít funkci
nejen dle jména, ale i dle typu: https://hoogle.haskell.org/
Je fakt dobrý! Když do něj hodíte něco jako
(Int -> String) -> [Int] -> [String], tak pořád pochopí že jde omap! - Naučte se používat své IDE! Pokud používate Haskell Language Server (což byste měli), tak se s ním naučte pracovat. Konkrétně když jste nad Typed Hole, tak můžete zavolat Code Action (kliknout na 💡).
Akce umí věci jako:
- "introduce lambda", což vyrobí nové argumenty
- "case split on <proměnná>", což vyrobí case-of
- "refine hole", které se snaží upřesnit výsledek
- a dokonce "attempt to fill hole", které vám občas napíše celou funkci samo!
- Jak vyvíjet pomocí typed holes [anglicky] -- https://reasonablypolymorphic.com/blog/typeholes/