Ahogy tanulgatom a monádikus kódok írását, azt veszem észre, hogy a Haskell nyelv rengeteg stílust megenged. Vegyünk egy példát: van egy Graph osztályunk, amiben van két lista, illetve két egész szám, amik meghatározzák, hogy a kövektkezőnek gráfhoz adandó élnek vagy pontnak mi lesz az indexe (ami után - ugyebár - meg kell növelni ezt az értéket eggyel). (Persze ez nem a leghatékonyabb, mert pl. törlésnél keletkeznek felhasználatlan indexek is, de most a példánál erre nem lesz szükségünk.) Az első lista minden eleme egy pár, aminek az első része egy gráfpont, a második pedig egy index (egész szám). A második lista is ugyanígy néz ki, csak a párok első eleme nem gráfpont, hanem gráfél. Minden gráfpontnak van egy pozíciója, és tartalmazza az összes belőle kiinduló él sorszámát. Az élek pedig tudják magukról, hogy kik a végpontjaik (tartalmazzák az indexüket).
A feladat: írjunk egy olyan függvényt, ami hozzáad egy pontot a gráfhoz (tehát fogja a pontot, hozzárendel egy indexet, a pontból és az indexből képez egy párt, majd kreál egy új gráfot, ami megegyezik a régi gráffal azt leszámítva, hogy az új gráf gráfpontokat tartalmazó listájában már benne van az új pont. Ja igen, ha Haskellben egy változót deklaráltál, az már úgy marad a program bezárásáig. Ezért is "pure" nyelv. Ezt úgy kerüljük ki, hogy létrehozunk egy új változót, és azt adjuk vissza. (Erre van az a vicc is, hogy ha a Haskell egy kocsi lenne, az úgy működne, hogy igazából nem is megy sehova, hanem végig másolatokat készít magáról az út mentén :D)).
A típusszignatúra a következő lesz:
addNode :: Node -> State Graph ()
Tehát az előző posztból ismerős State monádot fogjuk - kicsikét szelídebb célokra - használni. No hát akkor nézzük, hogy is nézne ki a kód do-jelölést használva:
addNode :: Node -> State Graph ()
addNode n = do
Graph nl el nni nei <- get
put (Graph ((n, nni):nl) el (nni + 1) nei)
Tehát először is kicsomagoljuk a gráfot, majd visszaadunk egy másikat, amiben már benne van az új pont. Jó tömören van megírva, nem fontos megérteni, csak a stílust kell figyelni. Na de a do-jelölés káros a kezdők számára, mert így könnyen megtéveszti az imperatív háttérrel rendelkező tanulókat, és rosszul fogják felhasználni, aztán csak tépik a hajukat, hogy mi van. Tehát írjuk meg anélkül.
Először is, használhatunk lambda függvényeket (tehát névtelen függvényeket), amikkel így nézne ki a kód:
addNode :: Node -> State Graph ()
addNode n =
get >>= \(Graph nl el nni nei) ->
put (Graph ((n, nni):nl) el (nni + 1) nei)
Aztán, valaki jobban szeret megmaradni a matematikai formuláknál, azoknak van kitalálva a let..in forma (az f (Graph...) rész nem fért ki egy sorba, ezért tördeltem):
addNode :: Node -> State Graph ()
addNode n =
let
f (Graph nl el nni nei) =
put (Graph ((n, nni):nl) el (nni + 1) nei)
in
get >>= f
Valaki viszont a matematika másik formáját szereti, amit még Iózanparasztiész görög matematikus-filozófus alkotott meg, és ami előnyben részesíti a definíciót, és csak utána a magyarázatot, tehát a "where" kulcsszót használja (szintén tördelve):
addNode :: Node -> State Graph ()
addNode n =
get >>= f
where
f (Graph nl el nni nei) =
put (Graph ((n, nni):nl) el (nni + 1) nei)
És akkor persze ez csak a State monád. Egyébként a do-jelölésnek megvan az az előnye, hogy hamarabb el lehet kezdeni működő kódot írni, mint hogy valaki teljesen megértené a monádok működését, ami számomra nagy előny, mert már így is késésben vagyok ezzel a nyamvadék városgennel. (Igen, ma van a határidő. Hát, vicces... >.<) De közben gyakorolgatok, különböző formákba írom át, meg vissza, hogy azért haladjak a megértéssel is. Jó móka :D