Hogyan segít a MONAD abban, hogy mellékhatása lehessen az amúgy pure function-öknek?





"csak szigorúan mellékhatásmentes függvényhívásokat enged meg"
Pont ez az, hogy az a cél, hogy ne csak ilyenek lehessenek.





Sajnos pontosan nem tudom megfogni, de néha úgy érzem, értem valamennyire, van ennek egy sajátos és teljesen korrekt logikája. Formailag az a lényeg, hogy az IO és a monászok teljesen korekt eszközök, nem lehet velük megsérteni a referenciális átláthatóság követelményét, tehát a dolog valódi, nincs benne csalás.
Vegyünk egy példát, amit mondtál is, legyen egy now függvény, ami a rendszeridőt adja ki mondjuk string formájában.
Függvényként now nem létezik: nem lehetséges
now :: String
sem
now :: () -> String
függvény, semmi ilyesmi, hisz ezek nem lennének determinisztikusak, egyértelműek, matematikai értelemben nem lennének függvények. Nincs és nem is lehet olyan függvény, ami indeterminisztikus, bizonytalan ,,értéket'' ,,adhatna vissza''.
Ettől még létezhet
now :: IO String
típusú ,,dolog'', ebből azonban nem tudunk értéket visszaadatni. Nem tudunk pl. olyan kifejezéseket építeni, hogy
"Today's date is " ++ now
, mert a monadikus ,,dolgokat'' nem lehet tiszta műveletekkel naív módon továbbépíteni. Nem tudjuk ,,becsapni'' a referenciális átlászóság követelményét. Egy ilyen monadikus ,,dologgal'' valójában csak dolgot tehetünk vele: a három monadikus kombinátor használatával (>>=, map, return) bekötjük valami még nagyobb IO ...-típusú kifejezésbe, de ebből szintugyanúgy nem tudunk közvetlenül értéket kinyerni, legfeljebb őket is újra tovább bekötjük még nagyobb IO ...-típusú kifejezésekbe, és így tovább. Bizonyos értelemben egy olyan dolog, ami IO-típusú, az már sosem szabadulhat az IO fogságából. Egészen addig, míg valamilyen formában el nem jutunk a main-ig.
Épp itt a lényeg: IO ... típusú kifejezésből közvetlenül sosem tudunk visszatérési értéket kinyerni, hanem kizárólag úgy, hogy valamilyen formában összeköttetésbe hozzuk a main-nel. A main-től függetlenül felhasználásra kerülő IO-típusú kifejezés nem létezik.
Tehát az IO-típusú dolgok nem függvények abban az értelemben, mint mint ahogy esetleg közvetlenül naivan gondolnánk. Pl. egy
Int -> IO Int
típusú dolog egyáltalán nem olyasmi, mint egy egész számot egész számra képező függvény. Ezek az IO-s dolgok sokkal inkább olyasvalamik, amiknek saját ,,mininyelvük'', önálló algebrájuk, domainspecifikus nyelvük van: saját monadikus kis ,,mininyelvük'' segítségével be lehet kötni egy sajátos IO-algebrai kifejezésbe, ahonnan szintén csak még további még nagyobb IO-kifejezések képezhetők, az IO-ból képtelenség ,,kikerülni'', a dolog mindvégig az IO ...-típus foglya marad, egészen fel a main-ig, ami azt jelenti, hogy bármiféle IO-kifejezés kényszerűen mindenképp csak mint a main valamilyen része létezhet.
Az IO ... típusú dogokra, illetve végső soron magára a main-re pedig gondolhatunk úgy, mintha valami
type IO a = World -> (a, World)
definíciója lenne ennek a rejtélyes IO .. typusnak, ahol a World a világ állapotát jelenti. A main tehát olyasféle függvényként képzelhető el, ami veszi a teljes Univerzum állapotát (beleértve a gépben bolyongó összes elektron összes lehetséges elhelyezkedését a gép huzalaiban, a felhasználó agyának összes szinapszisát, stb..., és a világ ismertnek feltételezett állapotához hozzárendel valamit (pl., az épp aktuálisan kidobott véletlenszámot), illetve a világ új, ,,megváltozott'' állapotát. Tisztán matematikai, filozófiai értelemben ez már tekinthető determinisztikus, tiszta függvénynek. Ha erre tisztán matematikailag gondolunk, ez tulajdonképp egy teljesen determinisztikus függvény. Akár a random számokra is értelmezhető: beeadjuk a gép összes elektronjának állapotát, abból elméletileg, filozófialilag le lehetne teljesen determinsztikusan, milyen véletlenszámot dobna a következő pillanatban a gép, aztán kiadjuk a gép összes elektronjának új állapotát.
Persze ez nagyon elrugaszkodott szemlélet, gyakorlatiasabb szemlélet lenne az, ha a Világra mint valami számelméleti pszeudorandom algoritmus seed-értékére gondolnánk, ahonnan már akár determinisztikusan le lehetne vezetni az épp aktuálisan dobandó véletlenszámot, és az új seed-et. (Tulajdonképpen a véletlenszámokra létezik is külön egy ilyen seed-alapú monász.) Így érthetővé válik, hogy legalábbis pszeudorandom számokat hogyan lehet tisztán funkcionális szemlélelettel értelmezni.
Valójában persze ez az egész World modell csak a megértés szempontjából érdekes, a valóságban egyszerűen csak arról van szó, hogy az IO típusú kifejezéseket a monadikus kombinátorok még nagyobb IO-típusú kifejezésekké láncolják össze, míg előbb-utóbb valamilyen formában eljutunk a main-ig. A main pedig végső soron összekapcsolódik a compiler által elérhető gépi részletekkel, amibe beleértendő bármi assemby-szinten elérhető dolog, akár a felhasználó által adott bemenet vagy akármilyen időszerű, memóriafüggő vagy random jellegű dolog is.
Tehát valójában ez a világ-hasonlat inkább elméleti modell, a gyakolatban a main-t a compiler köti be az alacsonyszintű gépi részletekbe, de a lényeg azonban ugyanaz: a referenciális átláthatóság teljesül, az IO-fogságából lehetetlen kikerülni, csak a main-en keresztül vehető ki belőle érték, a main pedig tekinthető az Univerzumot valamilyen értékes értékre és egy új Univerzumra leképző függvénynek.
Technikailag meg az egész jól megvalósítható úgy, hogy
1) csak és kizárólag a main pontján adunk hozzáférést a compilernek az alacsonyszintű gépi részletekhez.
2) az IO .. típusú dogokat csak három monadikus művelet segítségével kombinálhatjuk tovább, mást nemm lehet velük kezdeni, értéket kinyerni belőlük közvetlenül (a main-től független módon) nem lehet.





Ez amit próbáltam kifejezni (nem teljesen jól, mert olyan jól soem mélyítettem el), erre úgy érdemes rákereni, hogy
monad + state-of-the-world
ez egy kedvelt és gyakori szemléleti modell az Általad felvetett kérdéskör tárgyalására.
Van ennek a szemléletnek kritikája is:
de ennek ellenére ez egy értékes modell, amivel szerintem érdemes foglalkozni.





IO a típus jelentése: olyan ,,függvény'' típusa, ami a tár és a perifériák (IO eszközök) állapotához hozzárendel egy ,,a'' típusú értéket, és a tár+perifériák egy új állapotát.
A gyakorlatban bőven elég, ha az IO valójában a a fordító számára alacsonyszintű részletekkel közvetlen kapcsolatot tartó kódrészletek generálást jelzi.
A lényeg, hogy az IO a típust csak a monadikus operátorok révén használhatjuk, ez rákényszerít minket, hogy minden IO a típusú dolog a main függvényhez kapcsolódjék, önállóan használhatatlan legyen.
Ez szépen megvalósíható a compiler szintjén, és a kívánatosnak tartott tulajdonságok (szemantikai átláthatóság) nem sérülnek. Bizonyos értelemben a Haskell teljesen teljesíti azt az ígéretet, hogy csak tiszta függvények létezzenek.
A korrekt válasz a kérdésedre az, hogy: nem segít. :)
Egy félreértés az, hogy a monád oldja meg a mellékhatások problémáját. Ez csak egy véletlen egybeesés.
Nincs sok értelme egy olyan programnyelvnek, amiben nem lehet mellékhatása a függvényeknek, pl. mert nem lehet kommunikálni a külvilággal. Csak melegítené a szobát az a gép, ami ilyen szoftvert futtatna, de egyébként semmi hasznosat nem csinálna - hacsak nem a szoba fűtését akarod megoldani vele télen. A vaskalapos funkcionális nyelvekben -- mint a Haskell is -- van helye a mellékhatásoknak, csak precíz módon el szeretnénk különíteni őket a tiszta, csak a paramétereitől függő, matematikai függvényektől. Azt akarjuk elérni, hogy a mellékhatásos kód jól elkülönüljön a tiszta kódtól. Ezt Haskell-ben az IO nevű izé segítségével érték el (ami izé véletlenül éppen egy monád, de nem azért lett monád, mert mellékhatása van, hanem más, praktikus okokból). Lett volna más megoldás is arra, hogy ez a "tisztátalan" kód elkülönüljön a "szép" kódtól, de a Haskell kitalálói és fejlesztői ezt a monádokkal kikövezett utat választották. A cél a két típusú kód elkülönítése volt, a monád rész csak a hab lett a tortán, de a fontos itt a kétféle kód szeparálása.
A monádokra tekints úgy, mint egy absztrakt adattípusra. Mint valamiféle funkcionális design pattern-re, ha tetszik. FP-ben sok mindent monádokkal valósítunk meg, néha anélkül, hogy ez tudatos lenne. Sok mindent lehet csinálni monádokkal, és ez a legritkább esetben függ össze azzal, hogy olyan kódot szeretnénk írni, aminek mellékhatása van. Az, hogy Haskell-ben az IO egy monád, azt mellékhatások szempontjából tekintheted véletlen egybeesésnek is. Legfeljebb annyiban nem véletlen a dolog, hogy a monádok segítségével imperatív(nak tűnő) kódot írhatsz funkcionális nyelven, az IO pedig eléggé imperatív jellegű dolog, tehát egy monáddal megvalósítani az IO-t egy jó választásnak tűnik (kapod pl. hozzá ajándékba a "do notation"-t, ami olyan, mintha szekvenciális kódot írnál), de nem szükségszerűen egyetlen lehetséges megoldása a mellékhatásos és a mentes kód elkülönítésének.
Kapcsolódó kérdések:
Minden jog fenntartva © 2025, www.gyakorikerdesek.hu
GYIK | Szabályzat | Jogi nyilatkozat | Adatvédelem | Cookie beállítások | WebMinute Kft. | Facebook | Kapcsolat: info(kukac)gyakorikerdesek.hu
Ha kifogással szeretne élni valamely tartalommal kapcsolatban, kérjük jelezze e-mailes elérhetőségünkön!