Haskell Hero

Interaktivní učebnice pro začínající Haskellisty

Částečná aplikace

Úvodem

V jedné z prvních lekcí jsme si řekli něco o aritě funkce. O tom, že jsou funkce unární, binární, ternární, atd... Zde si ukážeme, jak se dá na aritu nahlížet jinak.

Mějme obecnou ternární funkci f x y z obecného typu f :: a -> b -> c -> d. Tato funkce dle našich dosavadních znalostí potřebuje ke svému plnému vyhodnocení tři argumenty.

Co se ale stane, když jí dáme jen jeden argument? Z ternární funkce f se stane binární funkce (f x) :: b -> c -> d.

Co se stane, když dáme binární funkci (f x) jeden argument? Stane se z ní unární funkce (f x y) :: c -> d.

A konečně, co se stane, když dáme unární funkci (f x y) jeden argument? Stane se z ní nulární funkce (f x y z) :: d.

Pokud tento výraz napíšeme do Hugsu, sám si jej ozávorkuje jako ((f x) y) z a provede postupnou aplikaci, která je popsána výše.

Příklad s (+)

(+) je sama o sobě binární funkce, která si bere dvě čísla, jež sečte.


Krabičkové znázornění funkce (+)

Co se stane, když jí dáme pouze jeden argument, například 5? Stane se z ní unární funkce ((+) 5), která si jako argument bere číslo, které přičte k číslu 5.


Krabičkové znázornění funkce (5+)

((+) 5) 3 ~> 5 + 3 ~> 8
((+) 5) 4 ~> 5 + 4 ~> 9
((+) 5) 6 ~> 5 + 6 ~> 11
Funkci ((+) 5) můžeme zapsat infixově jako (5+). Stejně jako (div 4) můžeme zapsat (4 `div`).

Jelikož je sčítání komutativní, je nám jedno, jestli napíšeme 3 + 5 nebo 5 + 3. Problém může nastat například při funkci (^), kdy už nám záleží na tom, zda se provede dvě na x-tou nebo x na druhou. Funkci, která realizuje výpočet dvě na x-tou zapíšeme (2^) a funkci, která počítá x na druhou zapíšeme (^2).

(2^) 5 ~>* 32
(^2) 5 ~>* 25

Příklad s zipWith

Řekli jsme si, že funkce zipWith je ternární. To znamená, že potřebuje tři argumenty ke svému vyhodnocení. Co se stane, když ji aplikujeme na jeden argument, například na funkci (*)? Stane se z ní binární funkce (zipWith (*)), která vezme dva seznamy, jejichž prvky mezi sebou vynásobí.

Co se stane, když funkci (zipWith (*)) aplikujeme například na seznam [1,2,3]? Stane se z ní unární funkce (zipWith (*) [1,2,3]), která udělá ze seznamu maximálně tříprvkový seznam tvořený prvním prvkem daného seznamu, dvojnásobkem druhého prvku a trojnásobkem třetího prvku.

Co se stane, když funkci (zipWith (*) [1,2,3]) aplikujeme na seznam [5,5,5,5]? Stane se z ní nulární funkce zipWith (*) [1,2,3] [5,5,5,5], což je plně vyhodnotitelný výraz, který se vyhodnotí na seznam [5,10,15].

zipWith
   :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith (++)
   :: [[a]] -> [[a]] -> [[a]]

zipWith (++) [[1,2], [3,4,5]]
   :: [[Integer]] -> [[Integer]]

zipWith (++) [[1,2], [3,4,5]] [[10,11,12], [13,14]]
   :: [[Integer]]
Poslední výraz se vyhodnotí následovně:
    zipWith (++) [[1,2], [3,4,5]] [[10,11,12], [13,14]]

~>* [[1,2] ++ [10,11,12], [3,4,5] ++ [13,14]]
~>* [  [1,2,10,11,12]   ,   [3,4,5,13,14]   ]