Nos, igen. Én is engedtem a sötét oldal csábításának. Bár, erre igazából büszke vagyok :D No de miről is van szó? Épp neki akartam állni debuggolni a programomat, amikor jött a hidegzuhany: itt én nem fogok debug printeket kiírni. Legalábbis úgy nem, ahogy eddig.
A probléma a következő: a Haskell nyelvben élesen el vannak választva a mellékhatásos és a mellékhatás nélküli függvények. Mellékhatás nélkülinek hívunk egy függvényt akkor, ha nem változtatja meg a környezete állapotát, miközben lefut. Ilyen mondjuk a matematikában használatos szinusz függvény: megkap egy számot, és visszaad egy másikat. Nem ír ki közben a terminálra semmit, nem indítja el a nyomtatót, nem olvasztja le a hűtőt, és nem vesz le pénzt a számládról (különben kevesen használnák :D). Na, a mellékhatásos függvények mindezt megtehetik. (Ebből már világosan látnia kell mindenkinek, hogy a mellékhatásos függvény a sátán műve :D) Valamint a mellékhatásos függvények meghívhatnak mellékhatás nélküli függvényeket és más mellékhatásos függvényeket is, míg a mellékhatás nélküli függvények csak mellékhatás nélküli függvényeket hívhatnak meg. Ez önmagában még nem lenne gond. Általában a Haskell programok 80%-a mellékhatás nélküli számítás, ami azért jó, mert egyrészt könnyebben párhuzamosítható, hiszen ezek a függvények nagyon jól elvannak egy-egy szálban, nem nyúkálnak a többihez. Másrészt pedig sokkal könnyebb belátni róluk, hogy helyesek, mivel nem függnek láthatatlan paraméterektől (igen, rád gondolok, globális változó!). A maradék 20% pedig végzi a piszkos munkát (IO, ilyesmik).
A probléma ott kezdődik, hogy ha valaki debuggolni akarja a progit. Elindítottam a programot, és nem akar leállni. Gyanítom, egy végtelen ciklusba keveredett. Szeretném, ha minden függvény kiírná, hogy őt most épp meghívták. Tehát azok a mellékhatás nélküli függvények, amik a program zömét teszik ki, mellékhatást kell okozniuk. Dehát ezt nem lehet! Na, ilyenkor mi a teendő?
Több megoldás közül választhatunk. Az egyik, hogy elsőre jól írjuk meg a kódot, és akkor nem kell debuggolni :D De ez túl egyszerű, lássuk be. Meg, akit órabérben fizetnek, tudja, hogy ez nem járható út :D A második lehetőség, hogy fáradságos munkával a függvények típusát átírjuk úgy, hogy mellékhatásos legyen, majd, ha sikerült kijavítani a hibát, visszaírjuk. Fárasztó rabszolgamunka. A harmadik, hogy használunk egy előre megírt könyvtárat (ilyen a hslogger), ami szép átgondolt keretrendszert kínál a számunkra. (Ez a javasolt megoldás.) De ez most nekem nem jó, mivel egyrészt időhiányban vagyok, másrészt nincs is szükségem ilyen komoly berendezésre. Ekkor jön a negyedik módszer, amit gyakorlatilag simán nevezhetünk hack-nek. Ugyanis elhitetjük a fordítóval, hogy a függvényünk, ami mellékhatásos, igazából nem mellékhatásos. Ezt úgy lehet megtenni, hogy lassan a fordító felé fordulsz, mélyen a szemébe nézel, majd lassan, jól hallhatóan és tagoltan odasúgod neki, hogy "unsafePerformIO". Ekkor ő egyetértően bólogat, és komoly arccal nekikezd fordítani. :D
Mikor érdemes az unsafePerformIO-t használni? Amikor olyan mellékhatásokat akarunk a programba injektálni, amiről biztosak vagyunk, hogy nem okoznak összeakadást. Ilyen pl. a debugprint az esetemben, mert soha nem lesz szükségem a terminálon szereplő dolgokra. Vagy használhatjuk olyankor is, amikor optimalizálni akarunk egy algoritmust (lásd: ByteString). De igazából ezt csak akkor szabad használni, ha tudod, mit csinálsz.
U.i.: Akarok egy ilyen pólót. (A felirat rajta: "Haskell Hackers - We're not afraid of a little unsafePerformIO!")
U.i.2.: Ha valaki esetleg Haskellban programozik, és nem tudna rájönni, hogy hogy is kell ezt megvalósítani, annak itt egy kis példakód:
import System.IO.Unsafe
debugIO :: IO a -> b -> b
debugIO a b = unsafePerformIO $ a >> return b
debugPrint :: String -> a -> a
debugPrint s a = debugIO (putStrLn s) a
Aztán, ha használni is akarod, akkor ilyen formában tudod megtenni:
add :: Int -> Int -> Int
add n1 n2 = debugPrint "Hello, world!" (n1 + n2)
Arra mindenesetre vigyázni kell, hogy az ilyen módon mellékhatásmentesített függvényekre is vonatkoznak a lusta kiértékelés szabályai, illetve ezek is ugyanúgy oiptimalizálva lesznek a fordító által, mint a többi mellékhatásmentes függvény, tehát semmi garancia nincs arra nézve, hogy lefut a kód. Erre problémára a strictness operátorokat javaslom.