Her çağrıldığında random
farklı bir sonuç verecek olan saf bir fonksiyon yaratamazsınız . Aslında, saf fonksiyonları “çağıramazsınız” bile. Onları uygula. Yani hiçbir şeyi kaçırmıyorsunuz, ancak bu, fonksiyonel programlamada rasgele sayıların sınırsız olduğu anlamına gelmez. Göstermeme izin ver, boyunca Haskell sözdizimini kullanacağım.
Zorunlu bir arka plandan geliyorsa, başlangıçta böyle bir türe sahip olmasını rasgele bekleyebilirsiniz:
random :: () -> Integer
Ancak bu zaten ekarte edildi çünkü rastgele saf bir işlev olamaz.
Bir değer fikrini düşünün. Bir değer değişmez bir şeydir. Asla değişmez ve bu konuda yapabileceğiniz her gözlem her zaman için tutarlıdır.
Açıkçası, rastgele bir Tamsayı değeri üretemez. Bunun yerine, bir Tamsayı rasgele değişken üretir. Bu tür şöyle görünebilir:
random :: () -> Random Integer
Bir argüman iletmenin tamamen gereksiz olması dışında, işlevler saf, bu yüzden biri diğeri random ()
kadar iyidir random ()
. Bundan sonra, bu tür rastgele vereceğim:
random :: Random Integer
Her şey iyi ve iyi, ama çok kullanışlı değil. Gibi ifadeler yazmayı bekleyebilirsiniz random + 42
, ancak yazamazsınız , çünkü o yazmaz. Henüz rastgele değişkenlerle bir şey yapamazsınız.
Bu ilginç bir soru ortaya çıkarır. Rasgele değişkenleri manipüle etmek için hangi fonksiyonlar bulunmalıdır?
Bu işlev var olamaz:
bad :: Random a -> a
Herhangi bir yararlı şekilde, çünkü o zaman yazabilirsiniz:
badRandom :: Integer
badRandom = bad random
Bu bir tutarsızlık getiriyor. badRandom sözde bir değer, ancak aynı zamanda rastgele bir sayıdır; bir çelişki.
Belki bu işlevi eklemeliyiz:
randomAdd :: Integer -> Random Integer -> Random Integer
Ama bu sadece daha genel bir model için özel bir durum. Bunun gibi diğer rastgele şeyleri almak için herhangi bir işlevi rastgele şeye uygulayabilmelisiniz:
randomMap :: (a -> b) -> Random a -> Random b
random + 42
Yazmak yerine şimdi yazabiliriz randomMap (+42) random
.
Sahip olduğunuz her şey randomMap olsaydı, rastgele değişkenleri bir araya getiremezdiniz. Örneğin, bu işlevi yazamazsınız:
randomCombine :: Random a -> Random b -> Random (a, b)
Böyle yazmayı deneyebilirsin:
randomCombine a b = randomMap (\a' -> randomMap (\b' -> (a', b')) b) a
Ancak yanlış tiptedir. Bunun yerine bir ile biten Random (a, b)
, bir ile bitirmekRandom (Random (a, b))
Bu, başka bir işlev ekleyerek düzeltilebilir:
randomJoin :: Random (Random a) -> Random a
Ancak, sonunda netleşebilecek nedenlerden dolayı, bunu yapmayacağım. Bunun yerine şunu ekleyeceğim:
randomBind :: Random a -> (a -> Random b) -> Random b
Bunun aslında sorunu çözdüğü hemen belli değil, ama çözüyor:
randomCombine a b = randomBind a (\a' -> randomMap (\b' -> (a', b')) b)
Aslında, randomBind'i randomJoin ve randomMap cinsinden yazmak mümkündür. RandomJoin'i randomBind cinsinden yazmak da mümkündür. Ama bunu bir egzersiz olarak yapmaktan ayrılacağım.
Bunu biraz basitleştirebiliriz. Bu işlevi tanımlamama izin ver:
randomUnit :: a -> Random a
randomUnit, bir değeri rastgele bir değişkene dönüştürür. Bu, aslında rastgele olmayan rastgele değişkenlere sahip olabileceğimiz anlamına gelir. Bu her zaman olsa da oldu; Daha randomMap (const 4) random
önce de yapabilirdik. RandomUnit'i tanımlamanın nedeni iyi bir fikirdir, şimdi randomMap'i randomUnit ve randomBind olarak tanımlayabiliriz:
randomMap :: (a -> b) -> Random a -> Random b
randomMap f x = randomBind x (randomUnit . f)
Tamam, şimdi bir yerlere geliyoruz. Değiştirebileceğimiz rastgele değişkenler var. Ancak:
- Bu fonksiyonları gerçekten nasıl uygulayabileceğimiz belli değil,
- Oldukça hantal.
uygulama
Sözde rasgele sayıları ele alacağım. Bu işlevleri gerçek rasgele sayılar için uygulamak mümkündür, ancak bu cevap oldukça uzadı.
Temel olarak, bunun işe yarayacağı yol her yerde bir tohum değeri geçireceğimizdir. Ne zaman yeni bir rastgele değer üretsek, yeni bir tohum üreteceğiz. Sonunda, rastgele bir değişken oluşturmayı tamamladığımızda, bu işlevi kullanarak ondan örnek almak isteyeceğiz:
runRandom :: Seed -> Random a -> a
Random türünü şöyle tanımlayacağım:
data Random a = Random (Seed -> (Seed, a))
O zaman, sadece oldukça basit olan randomUnit, randomBind, runRandom ve random uygulamalarını sunmamız gerekiyor:
randomUnit :: a -> Random a
randomUnit x = Random (\seed -> (seed, x))
randomBind :: Random a -> (a -> Random b) -> Random b
randomBind (Random f) g =
Random (\seed ->
let (seed', x) = f seed
Random g' = g x in
g' seed')
runRandom :: Seed -> Random a -> a
runRandom seed (Random f) = (snd . f) seed
Rastgele olarak, zaten türün bir işlevi olduğunu kabul edeceğim:
psuedoRandom :: Seed -> (Seed, Integer)
Bu durumda rastgele adil Random psuedoRandom
.
İşleri daha az hantal hale getirmek
Haskell, gözlerinde bu kadar güzel şeyler yapmak için sözdizimsel şekere sahiptir. Buna no-notasyon denir ve hepsini kullanmamız gerekir, bunu Rastgele için bir Monad örneği yaratırız.
instance Monad Random where
return = randomUnit
(>>=) = randomBind
Bitti. randomCombine
daha önce şimdi yazılmış olabilir:
randomCombine :: Random a -> Random b -> Random (a, b)
randomCombine a b = do
a' <- a
b' <- b
return (a', b')
Bunu kendim için yapsaydım, bundan daha bir adım öteye gider ve bir Uygulayıcı örneği yaratırdım. (Bu hiç mantıklı değilse endişelenmeyin).
instance Functor Random where
fmap = liftM
instance Applicative Random where
pure = return
(<*>) = ap
Sonra randomCombine yazılabilir:
randomCombine :: Random a -> Random b -> Random (a, b)
randomCombine a b = (,) <$> a <*> b
Şimdi bu örneklere sahip olduğumuz için >>=
, randomBind yerine kullanabilir, randomJoin yerine join, randomMap yerine fmap, randomUnit yerine return. Ayrıca tüm fonksiyon yüklerini ücretsiz olarak alıyoruz.
Buna değer mi? Rasgele sayılarla çalışmanın tamamen korkunç olmadığı bu aşamaya gelmenin zor ve uzun soluklu olduğunu iddia edebilirsiniz. Bu çaba karşılığında ne aldık?
En acil ödül, programımızın hangi bölümlerinin rasgeleliğe bağlı olduğunu ve hangi bölümlerin tamamen belirleyici olduğunu tam olarak görebilmemizdir. Tecrübelerime göre, böyle bir ayrılığı zorlamak, işleri çok basitleştirir.
Şimdiye kadar ürettiğimiz her rastgele değişkenden tek bir örnek istediğimizi varsaydık, ancak gelecekte daha fazla dağılım görmek istediğimizi ortaya çıkarırsak, bu önemsizdir. RunRandom'u farklı tohumlarla aynı rastgele değişkende birçok kez kullanabilirsiniz. Bu, elbette, zorunlu dillerde mümkündür, ancak bu durumda, rastgele bir değişkeni örneklediğimiz her seferde beklenmeyen G / Ç gerçekleştirmeyeceğimizden emin olabiliriz ve durumu başlatmak için dikkatli olmak zorunda değiliz.