• Nem Talált Eredményt

Haskell-listák

6.2. Haskell-monádok

Az eddig megírt Haskell-függvényeket konstans bemenetekre hívtuk, de természetesen felmerül az igény, hogy az adatokat billentyűzetről vagy ál-lományból olvassuk be, és a képernyőre vagy egy állományba írjuk ki. A Haskell, hasonlóan más programozási nyelvekhez, biztosítja az adatbevitel-hez és az adatkiíráshoz szükséges eszközöket, azonban ezt korántsem volt olyan egyszerű megoldani.

Az olvasás/írás (I/O) műveletek mechanizmusa nem illik bele a funk-cionális paradigmába, ezért egy olyan eszközt, olyan struktúrát kellett ki-dolgozni, amely alkalmas azI/Oműveletek hatékony használatára, anélkül, hogy az megsértené a funkcionális gondolkodásmódot. A Haskell esetében ezt egy monád-nak nevezett struktúra bevezetésével oldották meg. A mo-nádok alkalmazásával az I/Oműveletek mellett a tömbök, a hibakezelések hatékony használatát is megvalósították, azaz minden olyan programozási művelet, amely mellékhatással jár, alkalmazható lett a Haskellben.

A monád fogalma a kategóriaelméletből származik. A kategóriaelmélet a legáltalánosabb matematikai struktúrák közötti kapcsolatokat írja le, az

6.2. Haskell-monádok 127 ezzel kapcsolatos értelmezések, fogalmak pontos leírását pedig megtaláljuk Awodey könyvében[1]. A monád egy számítási adattípust definiál, ami azt jelenti, hogy megadjuk, hogy egy adattípus értékein milyen számításokat végezhetünk, és ezek a számítások hogyan kombinálhatók.

Mint ahogyan eddig is megfigyelhettük, egy Haskell-függvény kiértéke-lése nem a programozó által definiált műveletek egymás után való végrehaj-tásából áll, azonban azI/Oműveletek elvégzése nem oldható meg másként.

Monádok segítségével megadhatjuk, hogy azI/Oműveleteket hogyan kom-bináljuk, azaz milyen sorrendben végezzük el őket, ilyenformán az imperatív nyelvekhez hasonló módon lehet megadni ezen műveletek kiértékelési sor-rendjét.

Ebben a fejezetben elsőször a Monad m típusosztályról lesz szó, majd az IO monádot mutatjuk be, amelynek segítségével a Haskellben olvasás, írás műveleteket végezhetünk. Későbbi fejezetekben bevezetésre fog kerülni még aMaybe monád.

Az IO monád és a Maybe monád is a Monad m típusosztálynak a példányai, ahol a Monad m típusosztály a standard Prelude-ben a követ-kezőképpen van definiálva:

class Monad m where

(>>=) :: m a -> (a -> m b) -> m b return :: a -> m a

A fenti típusosztályban két művelet, azaz két függvény/operátor típus-deklarációját láthatjuk.

A >>= függvény számítások (akciók) láncolását teszi lehetővé. Több-ször fogjuk használni a későbbiekben, csak egyszerűbben, egydo kifejezést (blokkot, jelölést) fogunk használni helyette, ahogyan azt korábban is már mutattuk. A >>= függvény például do jelölésre átírva a következőképpen néz ki:

m >>= f = do x <- m f x

Ez azt jelenti, hogy először végrehajtjuk azmszámítási sorozatot, amelynek eredménye az x-be kerül, majd meghívjuk az f függvényt az x bemene-ten, amelynek eredménye végül a do blokk eredménye lesz. A következő olvasIr_ függvény egy karakterláncot vár a billentyűzetről, amelyet rög-tön a beolvasás után ki is ír a képernyőre, ahol az általunk beolvasott értéket most is és a következőkben is mindig dőlt betűkkel fogjuk megjeleníteni. A

kiíratáshoz a putStrLnfüggvényt alkalmaztuk, amely hasonló a putStr -hez, csak használatakor a kiíratást automatikusan egynew line, azaz egy új sor karakter is követi.

olvasIr_ :: IO ()

olvasIr_ = getLine >>= putStrLn

> olvasIr_

Hello Haskell!

Hello Haskell!

A dojelölést használva a következőképpen módosul a függvény:

olvasIr :: IO () olvasIr = do

x <- getLine putStrLn x

A >> függvény két akció együttes elvégzését teszi lehetővé, azaz először az első, és utána a második akció kerül végrehajtásra, ahol az első akció által meghatározott érték nem lesz releváns. A következő függvény két karakter-lánc kiírását végzi:

ir_ :: IO ()

ir_ = putStr "Hello " >> putStrLn " Haskell!"

> ir_

Hello Haskell!

Ennek a függvénynek is megadjuk a dojelöléssel megírt változatát:

ir :: IO () ir = do

putStr "Hello "

putStrLn " Haskell!"

A következőkben az akciók megadását, illetve az egymás után való lán-colásukat, ahogy eddig is tettük, ezután is egydoblokkban fogjuk megadni.

Megjegyezzük még, hogy minden akciót külön sorba kell írni.

A return függvény segítségével elérjük, hogy a paramétereként meg-adott adatotbecsomagoljuk egyIOmonádba. A következő függvény egy ka-rakterláncot olvas be a billentyűzetről, majd a beolvasott értéket areturn alkalmazásával becsomagolja egy IO monádba, ezért a függvény kimeneté-nek típusa IO Stringlesz.

6.2. Haskell-monádok 129 olvasInt_ :: IO String

olvasInt_ = do temp <- getLine return temp

> olvasInt_

12

"12"

A következőkben azolvasInt_által meghatározott értéket egy<-művelet segítségével lekérjük, mondhatjuk azt is, hogy kicsomagoljuk, és a kapott értéket Inttípusú adattá alakítjuk:

> x <- olvasInt_

12

> read x :: Int 12

AzInttípusú adattá való átalakítás elvégezhető a függvénytörzsben is, aho-gyan ezt a következő olvasInt függvényben láthatjuk. A read függvény által meghatározott érték lekérésétlet ... =jelölés használatával fogjuk megoldani, mert így jelezzük a Haskellnek, hogy most egy tiszta függvény kiértékelése következik, nem egy akció végrehajtása. Figyeljük meg, hogy a let-et ebben az esetben nem lokális definíció megadására használjuk, aho-gyan azt korábban tettük. Areturnalkalmazásával azolvasIntfüggvény kimenetét most is becsomagoljuk egyIOmonádba, a különbség azonban az, hogy azolvasIntkimenete most IO Inttípusú érték lesz.

olvasInt :: IO Int olvasInt = do

temp <- getLine

let x = read temp :: Int return x

> olvasInt 12

12

Figyeljük meg, hogy azolvasIr_,olvasIr,ir_, illetveirfüggvények kimeneti értékének típusa IO (), ami azt jelenti, hogy semmit, azaz ()-t eredményeznek a függvényhívások. Haskellben a tiszta függvények mindig meghatároznak egy értéket. AzI/Oműveletek során azonban ez nem mindig lehetséges, ezért erre az esetre definiálták az IO () típust. Egy függvény kimeneti értékének típusa akkor IO (), ha az akciók sorát egy olyan I/O

függvény zárja, amely kimeneti értékének típusa szintén IO (), vagy az utolsó akcióreturn ().