× Aktuálně z oboru

SHIELD Experience Upgrade 7 – méně hledání a více zábavy [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]
Celá zprávička [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]

Funkcionální programování - Typy

[ http://programujte.com/profil/20356-libor-skarvada/ ]Google [ ?rel=author ]       [ http://programujte.com/profil/118-zdenek-lehocky/ ]Google [ ?rel=author ]       9. 6. 2006       20 049×

Ve třetí části seriálu o funkcionálním programování si řekneme o typech výrazů, jak lze z jednodušších typů skládat typy složitější a také něco o polymorfismu.

V typovaných funkcionálních jazycích, k nimž patří i Haskell, má každý výraz, a tedy i každá hodnota, svůj typ. Například hodnota True má typ Bool, tj. typ všech logických hodnot, stejně jako hodnota False nebo jako výrazy   not True, x && y.

Celá čísla mají typ Int nebo Integer, desetinná čísla mají typ Float nebo Double, znaky mají typ Char.

Typ můžeme (trochu zjednodušeně) chápat jako množinu hodnot. Skutečnost, že výraz M má typ σ, zapisujeme M::σ. Má-li více výrazů, M1,…,Mk, stejný typ σ, lze psát M1,…,Mk::σ.

Například:


False, True :: Bool
'a', 'b' :: Char
1 :: Int
Tomuto zápisu se říká typová anotace. Definuje-li se nová hodnota, je dobrým zvykem ji typově anotovat:


pi :: Float
pi  = 3.14159265

prompt :: Char
prompt  = '?'

Typové anotace nejsou v Haskellu povinné. Předchozí definice mohly být zapsány bez nich, interpret nebo kompilátor si umí typy odvodit sám. Existuje však několik důvodů, proč typové anotace uvádět.

  • zdrojový text programu je čitelnější
  • v typově anotovaném programu se odhalí více typových chyb (typová chyba ve výrazu může být taková, že se pozná, až když je odvozený typ srovnán s typem deklarovaným, a zjistí se rozdíl)
  • v některých případech je výpočet podle programu s typovými anotacemi rychlejší, protože automaticky odvozený typ může být příliš obecný a přeložený program by předpokládal výpočet i s hodnotami, které se pak nikdy nevyskytnou<>

Kromě základních typů (Bool, Float,…) můžeme vytvářet typy složené pomocí tzv. typových konstruktorů. Nejdůležitějším typovým konstruktorem ve funkcionálním programování je typový konstruktor –> (šipka). Jsou-li σ, τ dva typy, pak σ–>τ je typ všech (unárních) funkcí, jejichž argument je typu σ a funkční hodnota (výsledek) je typu τ.

Jsou-li ρ, σ, τ tři typy, pak typ ρ–>σ–>τ je typem všech binárních funkcí (tj. funkcí dvou proměnných), jejichž první argument je typu ρ, druhý argument je typu σ a výsledek je typu τ.

Jsou-li σ1,σ2,…,σn,τ nějaké typy, pak σ1–>σ2–> ... –>σn–>τ je typ všech n-árních funkcí, jejichž argumenty mají po řadě typy σ1,…,σn a jejich funkční hodnota je typu τ.

Například typové anotace některých operací v Haskellu vypadají takto:


toUpper :: Char -> Char
isDigit :: Char -> Bool
not :: Bool -> Bool
(&&), (||) :: Bool -> Bool -> Bool
odd, even :: Int -> Bool

V následujících kapitolách poznáme další typové konstruktory, různé rarity, pomocí nichž se tvoří typy seznamů nebo typy uspořádaných n-tic. Nyní si však můžeme všimnout, že i základní typy jako Bool nebo Int jsou zvláštními případy typových konstruktorů – jsou to tzv. nulární typové konstruktory: abychom pomocí nich vyjádřili typ, stačí je napsat samy o sobě. Neaplikují se na žádný další typ (na rozdíl třeba od šipky, kterou musíme aplikovat na dva typy), proto jsou nulární.

Polymorfní typy

Zatím jsme se setkali jen s typy, které se nazývají monomorfní. Lze je použít vždy jen v kontextu jediného typu. Například monomorfní funkci (tj. funkci monomorfního typu) lze aplikovat vždy jen na argument jediného typu a typ jejího výsledku může být také jen jediný. Takovou monomorfní funkcí je třeba funkce toUpper. Její typ je Char–>Char. Jiným příkladem monomorfní funkce je logická spojka not. Její typ je Bool–>Bool.

Funkce id je tzv. identita. Je definovaná


id x = x

Jaký typ má tato funkce? Kdybychom identitu aplikovali jen na celá čísla, měla by typ Int–>Int. Kdybychom ji aplikovali jen na znaky, měla by typ Char–>Char, kdyby byla aplikována na funkce typu Float–>Bool, měla by sama typ (Float–>Bool) –> (Float–>Bool) atd. Chceme však, aby identita byla univerzálně aplikovatelná na jakoukoliv hodnotu jakéhokoliv typu. Jinými slovy, aby jednou byla typu Int–>Int, podruhé typu Char–>Char, potřetí (Float–>Bool) –> (Float–>Bool) a jindy jakéhokoliv jiného typu a–>a, kde a zastupuje libovolný typ.

Jazyky s parametricky polymorfními typovými systémy (a Haskell mezi ně patří) umožňují zavést tzv. polymorfní typy, v jejichž zápisu kromě typových konstruktorů vystupují i tzv. typové proměnné. Například zmíněná identita má typ


id :: a -> a

V typu a–>a je a typová proměnná zastupující libovolný typ. To znamená, že např. ve výrazu id 3 se typová proměnná a specializuje na typ Int (a celý výraz id 3 má typ Int), ve výrazu id '*' se specializuje na Char (a celý výraz má typ Char) atd.

Podobně jako id existují v Haskellu polymorfní funkce const, flip definované následovně:


const x y = x
flip f x y = f y x

Funkce const vrátí první ze svých dvou argumentů. Tyto dva argumenty však mohou být libovolného typu. Jediným omezením na typ funkce const tedy je, že výsledek musí být stejného typu jako první argument. Je tedy


const :: a -> b -> a

Všimněme si, že kdybychom místo a–>b–>a napsali a–>a–>a, řekli bychom tím, že oba argumenty funkce const musejí mít stejný typ. Tím bychom však typ funkce const zbytečně omezili, protože bychom vyloučili výrazy const toUpper 5 apod.

Funkce flip vyžaduje, aby jejím prvním argumentem byla binární funkce, a této funkci obrací pořadí argumentů. Například flip const True 8 → const 8 True → 8 nebo flip (-) 3 8 → (-) 8 3=8-3 → 5. Její typová anotace (přidělující funkci flip nejobecnější typ) je tedy


flip :: (a -> b -> c) -> (b -> a -> c)

Už dříve jsme se setkali s operátorem skládání funkcí (.)


(f . g) x = f (g x)

Jeho (nejobecnější) typ je


(.) :: (b -> c) -> (a -> b) -> (a -> c)

Tento polymorfní typ říká, že první dva argumenty operátoru (.) jsou funkce, ale typ b argumentu prvé funkce musí být stejný jako typ výsledku druhé funkce. Například ve výrazu (not . odd) 4 má operátor (.) monomorfní typ (specializovaný z polymorfního) (Bool–>Bool)–>(Int–>Bool)–>Int–>Bool, neboť typová proměnná a se specializuje na typ Int, typové proměnné b, c se obě specializují na typ Bool.

V zápisech typů v Haskellu rozeznáme typové proměnné od typových konstruktorů podle toho, že jejich jména vždy začínají malým písmenem. Jména typových konstruktorů jsou buď tvořena speciálními symboly (např. (–>)), anebo začínají velkým písmenem (např. Bool, Char).

V programu Hugs, interpretu Haskellu, si můžeme nechat vypsat typ libovolného výrazu (pokud je tento výraz správně utvořen). Povelem :t (vypiš typ) zjistíme například:


not . not :: Bool -> Bool
const :: a -> b -> a
flip const :: a -> b -> b

Poznamenejme ještě, že s pojmy polymorfismus a polymorfní typ se setkáváme i v objektových jazycích. Tam však nejde o polymorfismus parametrický, ale o tzv. polymorfismus inklusní, mající původ v tom, že jeden typ může být podtypem jiného typu, anebo jedna třída podtřídou jiné třídy. Naproti tomu parametrický polymorfismus ve funkcionálních jazycích spočívá v nahrazení parametru – typové proměnné – libovolným typem.

Kvalifikované typy

Takzvané kvalifikované typy jsou jistým zobecněním parametricky polymorfních typů: Parametricky (plně) polymorfní typ říká, že za typovou proměnnou lze dosadit zcela libovolný typ. Někdy je však vhodné polymorfismus omezit a za typovou proměnnou dovolit dosadit jen typy z určité množiny typů, neboli z typové třídy.

Nechme si vypsat třeba typ operace sčítání (+).


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

Tento zápis typu říká, že (+) je typu a–>a–>a , v němž za typovou proměnnou a lze dosadit každý takový typ, který náleží do typové třídy Num. V té jsou číselné typy Int, Integer, Float, Double, Rational a další (uživatelsky definované) typy do ní lze přidávat.

Podobně typ operace (<)  (menší než) je


(<) :: Ord a => a -> a -> Bool

V typové třídě Ord jsou takové typy, jejichž hodnoty můžeme lineárně uspořádat, např. většina číselných typů (ale už ne třeba typ komplexních čísel), nebo typ Char – znaky lze seřadit podle abecedy, ale také uspořádané dvojice či posloupnosti – ty lze řadit lexikograficky.

Řekli jsme, že kvalifikované typy jsou zobecněním polymorfních typů. Podle toho, kolik typů je v typové třídě, která typ kvalifikuje, můžeme vyjádřit „míru polymorfismu“ – celou škálu od monomorfních až k plně polymorfním typům. Když budeme mít typovou proměnnou b, která bude kvalifikovaná typovou třídou ClassBool a tato třída bude obsahovat jediný typ Bool, pak typ ClassBool b => b popisuje tentýž typ jako Bool. Naopak, když zavedeme typovou třídu All a řekneme, že má obsahovat všechny typy, pak třeba kvalifikovaný typ All c => c->c popisuje stejnou množinu typů, jakou popisuje plně parametricky polymorfní typ c->c. Toto je ovšem spíše jen teoretická úvaha – v obou případech použijeme raději monomorfní resp. plně polymorfní typ, než bychom zaváděli jednoprvkovou resp. univerzální typovou třídu; chceme jen ukázat, že kvalifikované typy jsou silným prostředkem schopným popsat jak monomorfní, tak polymorfní typy a navíc i cokoliv mezi tím.

Definice nových typů

V Haskellu lze také vytvářet nové uživatelské typy, anebo dávat nová jména existujícím typům. Nově pojmenovat typ lze pomocí deklarace type. Můžeme dát třeba nové jméno typu Bool nebo pojmenovat typ uspořádaných dvojic reálných čísel:


type  PravdivostniHodnoty  =  Bool
type  Complex  =  (Double,Double)

Takto lze zavádět i nové typové konstruktory:


type  BinarniOperace a  =  a -> a -> a

(.+.)  :: BinarniOperace Int
m .+. n  =  (m + n) `mod` 10

implikace  ::  BinarniOperace PravdivostniHodnoty
implikace p q  =  not p || q

Deklarací type pouze dáváme nové jméno existujícímu typu nebo typovému konstruktoru. Je však také možné zavést nový tzv. datový typ se vším všudy, zejména se všemi hodnotami tohoto typu. K tomu se však potřebujeme seznámit s datovými strukturami, jako jsou n-tice, seznamy, stromy… Proto si definice nových datových typů a datových struktur necháme na některou z dalších částí.

Shrnutí

  • Každá hodnota v Haskellu má svůj typ. Typy hodnot vyjadřujeme typovými anotacemi.
  • Nejjednodušší typy jsou vyjádřeny nulárními typovými konstruktory.
  • Složitější typy se tvoří ze základních pomocí dalších typových konstruktorů. Ve funkcionálním programování hraje klíčovou roli binární typový konstruktor (->).
  • Typy mohou být parametricky polymorfní, v jejich zápisu vystupují typové proměnné.
  • Polymorfismus může být ohraničen – kvalifikované typy zužují výběr typů, které lze dosadit za typové proměnné v zápisu typu.
  • Složitější typy nebo typové konstruktory si můžeme označit jménem pomocí deklarace type.

Článek stažen z webu Programujte.com [ http://programujte.com/clanek/2006060302-funkcionalni-programovani-typy/ ].