۲۲ - ۳اعداد تصادفی
مثلِ فصلِ قبل با یه مثالِ مفصّل شروع میکنیم. این مثال کمک میکنه یه کم با مسائلی که میشه با نوعداده ِ State حل کرد آشنا بشین.
در این مثال از نسخهی ۱٫۱ کتابخونه ِ random استفاده میکنیم.
اول یه نگاه به توابعی که میخوایم استفاده کنیم بندازیم. قبلاً تو فصلی که داربازی رو درست کردیم از کتابخونه ِ System.Random استفاده کردیم، اما برای این مثال از توابعِ متفاوتی استفاده میکنیم. تو این مثال نگرش کلی داریم؛ نمیخوایم وارد جزئیاتِ طرز کار این ژنراتورها بشیم.
System.Random برای ایجاد ِ مقادیرِ نیمهتصادفی طراحی شده. میشه اون مقادیر رو با تأمینِ یه مقدار دانهای بدست آورد، یا با استفاده از یه ژنراتوری که سیستم خودش راه میندازه. از کتابخونه اینها رو استفاده میکنیم:
۱.
StdGen یکی از تایپهاییه که اینجا میبینیم، و ضرب ِ دو مقدارِ Int32 ِه. پس یه مقدار از تایپِ StdGen همیشه متشکل از دو مقدارِ Int32 ِه. هردوشون مقادیرِ دانهای برای ایجاد ِ عددِ تصادفی ِ بعدیاند.
۲.
تایپِ تابعِ mkStdGen از این قراره:
mkStdGen :: Int -> StdGenاینجا با تعریفِ این تابع کاری نداریم، الان اون جزئیات مهم نیستن. این تابع یه آرگومانِ Int میگیره و به یه ژنراتور نگاشتش میده و یه مقدارِ StdGen برمیگردونه، که در واقع یه جفت از مقادیرِ Int32 ِه.
۳.
تایپِ تابعِ next از این قراره:
next :: g -> (Int, g)که g یه مقدار با تایپِ StdGen ِه. اون Int در اولِ توپل، عددِ نیمهتصادفی ایه که از مقدارِ StdGen ایجاد شده؛ عضوِ دومِ توپل یه مقدارِ StdGen ِ جدیده.
۴.
تایپِ تابعِ random از این قراره:
random :: (RandomGen g, Random a)
=> g -> (a, g)این هم شبیهِ next ِه، ولی با این تابع میشه مقادیرِ تصادفی ِ غیر از اعداد هم ایجاد کرد. این که چه توپِلی درست شه با تایپ تعیین میشه.
خوب حالا یه ذره طرز کارشون رو ببینیم:
Prelude> import System.Random
Prelude> mkStdGen 0
1 1
Prelude> :t mkStdGen 0
mkStdGen 0 :: StdGen
Prelude> let sg = mkStdGen 0
Prelude> :t next sg
next sg :: (Int, StdGen)
Prelude> next sg
(2147482884, 40014 40692)
Prelude> next sg
(2147482884, 40014 40692)اون تابعِ پشتپرده که مقادیرِ خروجی رو تعیین میکنه خالص ِه، به همین خاطر دو بار یک مقدار گرفتیم؛ تایپ اجازهی هیچ اثری رو نمیده تا اتفاقات عجیبغریب نیوفتن. یه نسخهی دیگه از sg با یه ورودیِ دیگه به StdGen تعریف کنین و ببینین چی میشه.
نهایتاً به یه مقدار به اسمِ next sg رسیدیم. حالا اگه بخوایم با استفاده از اون عددِ تصادفی ِ بعدی رو ایجاد کنیم، باید مقدار StdGen از اون توپل رو دوباره به next بدیم. با تابع snd میشه اون مقدار رو بیرون کشید و به عنوانِ ورودی به next داد:
Prelude> snd (next sg)
40014 40692
Prelude> let newSg = snd (next sg)
Prelude> newSg
newSg :: StdGen
Prelude> next newSg
(2092764894,1601120196 1655838864)اگه دوباره newSg رو به next بدین باز هم همون جواب رو میگیرین، اما میشه دوباره مقدارِ StdGen رو استخراج کرد و به next داد تا یه توپل ِ جدید گرفت:
Prelude> next (snd (next newSg))
(1679949200,1635875901 2103410263)حالا چندتا مثال از random ببینیم. چون random میتونه از تایپهای مختلف مقادیر ایجاد کنه، باید تعیین کنیم از چه تایپی استفاده کنه:
Prelude> :t random newSg
random newSg :: Random a => (a, StdGen)
Prelude> random newSg :: (Int, StdGen)
(138890298504988632,439883729 1872071452)
Prelude> random newSg :: (Double, StdGen)
(0.41992072972993366,439883729 1872071452)سادهست، اگه یک عدد از یه بازه بخوایم چطور؟
Prelude> :t randomR
randomR :: (RandomGen g, Random a)
=> (a, a) -> g -> (a, g)
Prelude> randomR (0, 3) newSg :: (Int, StdGen)
(1,1601120196 1655838864)
Prelude> randomR (0, 3) newSg :: (Double, StdGen)
(1.259762189189801,439883729 1872071452)باید حالت ِ جدیدِ ژنراتور ِ عددِ تصادفی رو به تابعِ next بدیم تا یه مقدارِ جدید بگیریم:
λ> let rx :: (Int, StdGen); rx = random (snd sg3)
λ> rx
(2387576047905147892,1038587761 535353314)
λ> snd rx
1038587761 535353314اینطور زنجیرکردنِ حالت ممکنه خستهکننده شه. هدفِ ما تو این فصل حلِ این مسئلهست.