(pořadí vyhodnocování, striktní a normální redukce, líná redukce, efektivita, nekonečné datové struktury, definice funkcí nad nekonečnými strukturami)
Směr | Priorita | Operátor | Popis |
---|---|---|---|
← | 9 | . | skládání funkcí |
→ | 9 | !! | indexování prvku seznamu |
← | 8 | ^ | umocňování |
→ | 7 | * / 'div' 'mod' | multiplikativní aritmetické operátory |
→ | 6 | + - | aditivní aritmetické operátory |
← | 5 | : ++ | přidání prvku, spojení seznamů |
- | 4 | == /= < <= > >= 'elem' 'notElem' | relační operátory |
← | 3 | && | logická konjunkce |
← | 2 | || | logická disjunkce |
Výrazy se skládají z aplikací funkcí a operátorů na argumenty a z lokálních definic.
Mějme funkci
A výraz tvaru
Redukční krok je úprava výrazu, při které se některý z jeho podvýrazů nahradí pravou stranou definice funkce, tedy výrazem , v němž se každý výskyt proměnně nahradí odpovídajícím výrazem . Redukčním krokem nazýváme i takové transformace, při kterých v jeho podvýrazu vyčíslíme jednoduchou operaci aplikovanou na jednoduché argumenty (ať už operaci aritmetickou či logickou).
Redukční krok vlastně znamená jeden krok zjednodušení výrazu, často se značí ~> (P se redukuje na Q).
Výrazy můžeme vyhodnocovat obecně mnoha způsoby, způsob vyhodnocování je určen redukční strategií. Redukční strategie je pravidlo, které pro každý výraz jednoznačně určuje první redukční krok. Tedy redukční strategie určuje, který podvýraz se bude redukovat jako první.
Volba redukční strategie může ovlivnit, zda vůbec dospějeme k výsledku. Nemůže se však stát, abychom dvěma různými strategiemi dospěli k různým výsledkům.
Striktní redukční strategie je taková strategie, při které vyhodnocujeme nejdříve argumenty, tedy aplikaci upravujeme tak, že nejdříve, pokud to jde, redukujeme podvýraz použitím striktní strategie. Pokud nelze, redukujeme striktně , pokud také nelze tak a tak dále. Pokud nelze redukovat žádný z podvýrazů , tak redukujeme striktní strategií podvýraz . Pokud ani ten nelze, znamená to, že již nelze zjednodušit a celý výraz nahradíme pravou stranou jeho definice s dosazením hodnot výrazů za její parametry.1)
sq x = x * x
Výraz sq(sq 3) se při použití striktní redukční strategie bude vyhodnocovat:
sq (sq 3) ~> sq (3 * 3) ~> sq 9 ~> 9 * 9 ~> 81
f x = f x const x y = x
Ve výrazu const 3 (f 1) se bude podvýraz f 1 při použití striktní redukční strategie vyhodnocovat nekonečně dlouho, tedy výpočet se zacyklí a k výsledku nedojdeme nikdy:
const 3 (f 1) ~> const 3 (f 1) ~> const 3 (f 1) ~> ...
eat x y = x && (x || y)
Redukujeme-li výraz eat (not False) (not True) striktně, vyhodnotí se každý z argumentů jednou.
Normální redukční strategie je taková, při které aplikaci upravujeme tak, že nejdříve redukujeme (pokud to jde) podvýraz použitím normální strategie, pokud to nejde, znamená to že M již nelze zjednodušit a celý výraz nahradíme pravou stranou její definice s dosazením výrazů za její parametry.2)
sq x = x * x
Výraz sq(sq 3) se při použití normální redukční strategie bude vyhodnocovat:
sq (sq 3) ~> sq 3 * sq 3 ~> (3 * 3) * sq 3 ~> 9 * sq 3 ~> 9 * (3 * 3) ~> 9 * 9 ~> 81
f x = f x const x y = x
Výraz const 3 (f 1) se při použití normální redukční strategie bude vyhodnocovat:
const 3 (f 1) ~> 3
eat x y = x && (x || y)
Redukujeme li výraz eat ( not False) ( not True) normálně, vyhodnotí se první argument dvakrát a druhý se nevyhodnotí ani jednou.3)
Jedná se o modifikovanou verzi normální redukční strategie, využívající faktu, že funkcionální jazyky jsou referenčně transparentní, tedy že jakýkoli výraz se ve stejném prostředí vyhodnotí vždy stejně, vyhýbá se tak opakovanému vyhodnocování stejných podvýrazů.
Líná redukční strategie je taková, při které aplikaci upravujeme tak, že nejdříve redukujeme, pokud to jde, podvýraz M použitím líné strategie. Když M redukovat nelze, znamená to, že M je funkce a celý výraz nahradíme pravou stranou její definice s dosazením výrazů za její parametry stejně jako u normální redukční strategie. Nyní si však při každém násobném dosazení zapamatujeme, které podvýrazy vzniklé dosazením za různé výskyty téže proměnné jsou stejné, a při jejich případném dalším vyhodnocování je redukujeme jen jednou, jakoby všechny naráz.4)
eat x y = x && (x || y)
Redukujeme li výraz eat (not False) (not True) líně, vyhodnotí se první argument jednou a druhý ani jednou.
Striktní vyhodnocování bývá často v porovnání s normálním vyhodnocováním časově efektivnější, protože argumenty jsou vyhodnocovány jen jednou, zatímco v normálním vyhodnocování jsou argumenty vyhodnocovány tolikrát, kolikrát na to při výpočtu dojde.
Líné vyhodnocování je časově efektivní, protože se v něm každý argument vyhodnotí nejvýše jedenkrát (díky referenční transparentnosti funkcionálních jazyků).
Líné vyhodnocování nám umožňuje pracovat s funkcemi, které jsou definovány na nedefinovaných argumentech. Například máme-li funkci:
const x y = x
Pak výraz const 1 (1 / 0) má dobře definovanou hodnotu 1, přestože hodnota podvýrazu (1 / 0) není definována. Tato nedefinovanost nevadí, protože podvýraz se vůbec nevyhodnotí. Nedefinovaná hodnota nemusí být způsobena jen dělením nulou, ale obvykle se považuje za význam cyklícího výpočtu.
Užitečným příkladem nedefinovaných podvýrazů jsou nekonečné datové struktury, zejména seznamy. Například předpis:
ones = 1 : ones
definuje nekonečný seznam samých jedniček. Vyhodnocení výrazu ones se zacyklí a při každém kroku vytváří delší a delší seznam jedniček.
ones ~> 1 : ones ~> 1 : 1 : ones ~> 1 : 1 : 1 : ones ~> ...
Avšak vyhodnocení výrazu take 2 ones probíhá díky línému vyhodnocování konstruktoru (:) takto
take 2 ones ~> 1 : take (2 - 1) ones ~> 1 : take 1 ones ~> 1 : 1 : take (1 - 1) ones ~> 1 : 1 : take 0 ones ~> 1 : 1 : [] = [1, 1]
Toto vyhodnocení interpretujeme jako extrakci dvouprvkového seznamu ze začátku nekonečného seznamu složeného ze samých jedniček.5)
Užitečnými funkcemi pro vytváření nekonečných seznamů jsou repeat, cycle a iterate6)
repeat
repeat x = s where s = x : s
funkce repeat vytváří seznam, do kterého do nekonečna přidává prvek z argumentu x.
cycle
cycle t = s where s = t ++ s
funkce cycle vytváří seznam, do kterého do nekonečna připojuje seznam z argumentu t.
iterate
iterate f x = x : iterate f (f x)
funkce iterate vytváří z funkce f a prvku x seznam:
[x, f x, f (f x), f (f (f x))...]
Seznam všech čísel od 0 do nekonečna
Seznam všech druhých mocnin
map square [0..]
Případně také intenzionálním zápisem:
[x^2 | x <- [0..]]
Nekonečný seznam seznamů čísel reprezentující tabulku všech mocnin
Seznam Fibonacciho čísel
fib = 1 : 1 : zipWith (+) fib (tail fib)
Funkce zipWith aplikuje zadanou operaci na odpovídající prvky dvou zadaných seznamů a z výsledků těchto aplikací vytvoří seznam.
Například zipWith (*) [1, 2, 3] [1, 4, 9] *~> [1, 8, 27]7)
Vhodné doplnění studia může být vyzkoušení příkladů ze cvičení prakticky v interpretu Haskellu Hugs.
Nebo třeba trošku populárněji psaný seriál o funkcionálním programování na Programujte.com.
Měli byste vědět, jak souvisí tabulka priorit operátorů s redukční strategií.
Winsik - ICQ: 215787520 mail: winsik.cz(zavinac)gmail.com
Otázku jsem připravil podle sebe a jsem si vědom toho, že někomu může něco chybět, nebo že se někomu nebude líbit struktura nebo grafické zpracování. Proto pokud chcete, můžete samozřejmě sami otázku upravit, doplnit co podle vás chybí, kontaktovat mne, a třeba připomínku vyjádřit v diskuzi, budu jen rád.
Otázku si přečetl pan RNDr. Libor Škarvada a rámcově prošel. Jeho podněty pro doplnění textu, opravy nesrovnalostí a odstranění matoucích či k otázce se nevztahujících textů byly do otázky zaneseny. Tato kontrola je jen rámcová, stále se může stát, že v otázce zůstala zapomenutá chybka či nesrovnalost, vyučující za toto nenese odpovědnost, berte tuto rámcovou kontrolu jako formu pomoci od vyučujících pro studenty.
Diskuze
»Mějme funkci eat definovanou :
je to pravda? ja si myslim, ze se vyhodnoti prvni dvakrat a druhy jednou. Do prvniho x se musi dosadit. tj nejprve overime toto ( ( not False ) || ( not True ) ). Po tomto ale jste nevime, jakou bude mit celkovy vyraz hodnotu, proto musime udelat jetse ( not False ) && ( ( not False ) || ( not True ) ). Nebo se pletu?
Já si myslím, že je to pravda.
Když si přečteš definici tak ta říká: když máš M N1…Nn tj.
Jaj, nevím, proč se mi ta odpověď tak divně zobrazila. snad se v tom vyznáš.
Jj, vyznám dík. Teď jak jsi to napsal, tak je to jasné. Ani nechápu, jak jsem se k tomu mému číslu dobral….:( Ještě jednou díkes.
jo, a u tech jednicek (ones) je jedno, zda jde o normalni ci line vyhodnocovani. Vzdy se to vyhodnoti. Jen u striktniho se zacyklim. Chapu spravne?
Dekuju
pokud myslíš čistě ones, ne tu s tou funkcí take 2, tak to cyklí pořád, ať použiješ jakoukoli redukční strategii.
myslím samozřejmě tu s tou take 2 ones. Sorry, jsem se spatne vyjadril
Docela mi tu chybí ta první část otázky - pořadí vyhodnocování. Způsob redukce totiž přímo navazuje na pořadí vyhodnocování(() je před *, * je před + atd..) a bez toho se těžko vyřeší složitější příklady… Pěkně to tuším bylo v těch skriptech co byly psané rukou, zkusím je někde najít, pokud to někdo znáte, tak to sem prosím napište.
hint –- slajd z prvnich nebo druhych cvik, ale bohuzel nemam cas jej hledat
este stastie ze som ten slajd mal zalozeny v skriptach :)
Doplnil jsem k normálnímu vyhodnocování informaci o efektu tzv. zkráceného vyhodnocování, protože by z toho jinak nemuselo být hned jasné, proč se druhý argument funkce eat nevyhodnotí ani jednou. Doufám, že jsem ten důvod toho správně pochopil.
opravil jsem směr vyhodnocování logické konjunkce a disjunkce, ← to určitě být nemá (viz poznámka 3). u multiplikativních operátorů mám taky pochybnosti, proč je to ←, ale tuším, že třeba prolog to tak má, haskell jsem nikde nedohledal…
přidána sekce Co byste ještě měli znát?
V sekci s línou redukční strategií je možné pracovat s nekonečnými datovými strukturami. Neplatí to ale i pro normální red. strategii?
To by me taky zajimalo…
Platí. Líná redukční strategie je ale ovšem oproti normální někdy efektivnější, protože použivá mezivýsledky.
„Měli byste vědět, jak souvisí tabulka priorit operátorů s redukční strategií.“
jak to souvisí?:)
Tipuji, že člověk, který to napsal, měl na mysli, že tabulka priorit operátorů určuje pořadí vyhodnocování.