跳转到内容

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'
华夏公益教科书