跳轉到內容

Haskell/解決方案/理解 Monad

來自華夏公益教科書

隨機數生成

[編輯 | 編輯原始碼]
練習
  1. 擲兩個骰子!使用sumTwoDice,就是這個意思:-)。使用fst提取結果。
  2. 編寫一個函式rollNDice :: Int -> Seed -> ([Int],Seed),它擲骰子 n 次並返回一個包含 n 個結果的列表。額外:如果您知道無限列表,請使用unfoldrtake獲取結果列表(但這次沒有種子)。
  3. 使用來自System.RandomStdGenrandom重新實現SeedrollDie
  4. 現在您有了隨機數,請藉助rollNDice進行一些統計實驗。例如,對rollDie進行健全性檢查,以確保它沒有偏差,並以相同的可能性返回每個數字。雙骰子擲出的點數之和是如何分佈的?差異?以及三個骰子呢?

2. 使用章節中定義的randomNextSeedrollDie

rollNDice :: Int -> Seed -> ([Int], Seed)
rollNDice 0 seed = ([], seed)
rollNDice n seed = let (die,  seed0) = rollDie seed
                       (rest, seed1) = rollNDice (n-1) seed0
                   in ((die : rest) ,  
                       seed1)

該函式獲得第一個骰子和種子,然後遞迴呼叫自身以擲出 N 個骰子,一旦RollNDice到達基本情況,它就會反過來構建一個骰子列表。然後,該函式返回骰子列表和傳遞給基本情況的最終種子rollNDice 0 seed = ([], seed)

3. 我們使用 mkStdGen 獲取計算機的隨機數生成器,併為它提供一個任意數字 232。首先,在檔案開頭使用import System.Random

type Seed = StdGen
rollDie1 :: Seed -> (Int, Seed)
rollDie1 seed = randomR (1,6) seed

在 Prelude 中

Main> rollDie1 (mkStdGen 232)
(3,1017593109 40692)

使用bind穿插狀態

[編輯 | 編輯原始碼]
練習
  1. 使用>>=return實現上一節中的rollNDice :: Int -> Random [Int]

1. 您之前已經見過(>>==)定義下面的所有內容。因此,直到那一點的所有內容都只是為了設定一切。我將>>=命名為>>==,將return命名為return1,因為本章專門使用bindreturn來處理隨機數;而真正的bindreturn更加通用,因此出於某些原因無法使用 -----------------> 在那裡。至少它有效;您還想從我這裡得到什麼?鮮血?

在檔案開頭使用import System.Random

type Seed = StdGen
type Random1 a = Seed -> (a, Seed)

rollDie :: Random1 Int 
rollDie seed = randomR (1,6) seed

(>>==) :: Random1 a -> (a -> Random1 b) -> Random1 b
(>>==) m g = \seed0 -> 
  let (result1, seed1) = m seed0
      (result2, seed2) = (g result1) seed1
  in (result2, seed2)

return1 :: a -> Random1 a
return1 x = \seed0 -> (x, seed0)

rollNDice1 :: Int -> Random1 [Int]
rollNDice1 0 = return1 []
rollNDice1 n = rollDie >>== (\d1 -> rollNDice1 (n-1) >>== (\rest -> return1 (d1 : rest)))

在 prelude 中

 
*Main> rollNDice1 20 (mkStdGen 231)
([4,3,1,1,2,3,5,4,2,5,6,4,6,2,4,5,6,5,1,5],1927676552 238604751)

如果您想知道:您將(mkStdGen 232111)傳遞給rollNDice1,因為rollNDice 20本身只會建立一個Random1 [Int];您需要將新形成的Random1 [Int]傳遞一個種子,以(mkStdGen 242)的形式,才能讓它開始輸出隨機數。

輸入/輸出需要bind

[編輯 | 編輯原始碼]
練習
  1. 編寫一個函式putString :: String -> IO (),它使用putChar輸出一系列字元。
  2. 程式迴圈 :: IO () loop = return () >> loop 會永遠迴圈,而loopX :: IO () loopX = putChar 'X' >> loopX 會列印一個無限的X序列XXXXXX...。顯然,使用者可以透過檢視螢幕輕鬆區分它們。但是,證明模型IO a = World -> (a, World)為兩者都提供了相同的指稱⊥。這意味著我們必須放棄這個模型作為IO a的可能語義。

1.

putString :: String -> IO ()
putString [] = putChar '\n'
putString (s:xs) = putChar s >> putString xs

狀態 Monad

[編輯 | 編輯原始碼]
練習
修正(>>=)的定義,以考慮State建構函式。您需要使用模式匹配來刪除State建構函式。
(State a) >>= f = State $ \s -> let (a',s') = a s
                                    (State b) = f a'
                                in b s'
練習
  1. 稍微改寫(>>=)的定義,以使用runState而不是對State進行模式匹配。
a >>= f = State $ \s -> let (a',s') = runState a s
                        in runState (f a') s'
華夏公益教科書