Bir işlevin yan etkileri olduğunu varsayalım. Ürettiği tüm efektleri giriş ve çıkış parametreleri olarak alırsak, işlev dış dünya için saftır.
Yani, saf olmayan bir işlev için
f' :: Int -> Int
RealWorld'ü değerlendirmeye ekliyoruz
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.
sonra f
tekrar saf. Parametreli bir veri türü tanımlıyoruz type IO a = RealWorld -> (a, RealWorld)
, bu yüzden RealWorld'ü birçok kez yazmamız gerekmiyor ve sadece yazabiliyoruz
f :: Int -> IO Int
Programcı için, bir RealWorld'ü doğrudan yönetmek çok tehlikelidir - özellikle, bir programcı ellerini RealWorld türünde bir değere alırsa, kopyalamayı deneyebilir , bu temel olarak imkansızdır. (Örneğin, tüm dosya sistemini kopyalamaya çalışın. Nereye koyarsınız?) Bu nedenle, IO tanımımız tüm dünyanın durumlarını da kapsar.
"Saf olmayan" fonksiyonların bileşimi
Eğer onları zincirleyemezsek, bu saf olmayan işlevler işe yaramaz. Düşünmek
getLine :: IO String ~ RealWorld -> (String, RealWorld)
getContents :: String -> IO String ~ String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO () ~ String -> RealWorld -> ((), RealWorld)
Biz istiyoruz
- olsun konsolundan bir dosya adı,
- o dosyayı oku ve
- o dosyanın içeriğini konsola yazdırın .
Gerçek dünya devletlerine erişebilseydik nasıl yapardık?
printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
(contents, world2) = (getContents filename) world1
in (putStrLn contents) world2 -- results in ((), world3)
Burada bir model görüyoruz. Fonksiyonlar şöyle adlandırılır:
...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...
Böylece ~~~
onları bağlamak için bir operatör tanımlayabiliriz :
(~~~) :: (IO b) -> (b -> IO c) -> IO c
(~~~) :: (RealWorld -> (b, RealWorld))
-> (b -> RealWorld -> (c, RealWorld))
-> (RealWorld -> (c, RealWorld))
(f ~~~ g) worldX = let (resF, worldY) = f worldX
in g resF worldY
o zaman yazabiliriz
printFile = getLine ~~~ getContents ~~~ putStrLn
gerçek dünyaya dokunmadan.
"Impurification"
Şimdi dosya içeriğini de büyük harf yapmak istediğimizi varsayalım. Üst katmanlama saf bir işlevdir
upperCase :: String -> String
Ama bunu gerçek dünyaya dönüştürmek için geri dönmek zorunda IO String
. Böyle bir işlevi kaldırmak kolaydır:
impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)
Bu genelleştirilebilir:
impurify :: a -> IO a
impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)
öyle ki impureUpperCase = impurify . upperCase
yazabiliriz
printUpperCaseFile =
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn
(Not: Normalde yazıyoruz getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)
Baştan beri monadlarla çalışıyorduk
Şimdi ne yaptığımızı görelim:
(~~~) :: IO b -> (b -> IO c) -> IO c
İki safsız fonksiyonu bir araya getiren bir operatör tanımladık
impurify :: a -> IO a
Saf bir değeri saf olmayan hale getiren bir fonksiyon tanımladık .
Şimdi kimlik yapmak (>>=) = (~~~)
ve return = impurify
ve görüyor musunuz? Bir monadımız var.
Teknik not
Gerçekten bir monad olduğundan emin olmak için hala kontrol edilmesi gereken birkaç aksiyom var:
return a >>= f = f a
impurify a = (\world -> (a, world))
(impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world )) worldX
in f resF worldY
= let (resF, worldY) = (a, worldX)
in f resF worldY
= f a worldX
f >>= return = f
(f ~~~ impurify) worldX = let (resF, worldY) = f worldX
in impurify resF worldY
= let (resF, worldY) = f worldX
in (resF, worldY)
= f worldX
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
Egzersiz olarak bırakıldı.