了解Haskell随机硬币生成代码

时间:2018-06-22 05:55:47

标签: haskell

请帮助我理解下面的haskell代码。

data Coin = Heads | Tails deriving ({-hi-}Eq, {-/hi-} Show,Enum,Bounded)

instance Random Coin where
    randomR (a,b) g =
        case randomR (fromEnum a, fromEnum b) g of
          (x,g') -> (toEnum x,g')
    random g = randomR (minBound,maxBound) g

coins = do
    g <- newStdGen
    print . take 10 $ (randoms g::[Coin])

count = 10000

process :: [Coin] -> (Int,Int)
process cs = (length cs,length (filter (== Heads) cs))

display::(Int,Int) -> String
display (coins,heads) = "We got " ++ (show $ 100.0 * fromIntegral heads / fromIntegral coins) ++ "% heads. "

r = do
    g <- newStdGen
    putStrLn . display .process . take count $ randoms g
  1. 我无法理解{-hi-}Eq, {-/hi-} Show的含义。

  2. 及以下内容,“ of”和下一部分的意义是什么。

case randomR(fromEnum a,fromEnum b)g的           (x,g')->(toEnum x,g')

2 个答案:

答案 0 :(得分:6)

Coin是具有两个构造函数HeadsTails的代数数据类型,表示具有两个值的枚举。它与Bool(具有相同的结构)是同构的,但是是独特的类型。

deriving (Eq, Show, Enum, Bounded)自动生成类型类的实现:

  • Eq,这是支持测试与==相等的类型的类型

  • Show,用于将值转换为字符串以进行调试

  • Enum,用于使用前置函数和后继函数predsucc

  • 枚举值
  • Bounded,类型分别为minBound(此处为HeadsmaxBoundTails

  • {--}只是注释,编译器会忽略它;看来作者打算使用某种无法正常工作的非Haskell格式表示法。

instance Random Coin where开始为Random类型实现Coin类型类,从而实现硬币的随机生成。它具有两种方法的实现:

  • randomR (a, b) g描述了如何使用随机生成器a生成bg范围内的随机值。该实现调用randomR使用a类在参数bEnum的范围内生成随机 integer 。如果aHeads,则fromEnum a0;如果bTails,则fromEnum b1caseof表示法对该函数的结果执行 pattern match ,获得一对随机值x和更新后的随机生成器{{ 1}}(读作“ G素数”)。然后,它使用g'x从整数转换回Coin,并返回硬币值和更新的生成器。

  • toEnum描述了如何仅从生成器生成随机硬币而不将范围作为输入。它使用给定的生成器randomminBoundHeads)和maxBoundTails)之间产生硬币抛掷。

g是一个coins动作,它使用IO创建一个新的标准随机生成器,然后使用{{1}从该生成器生成一个无限的随机抛硬币列表(流) }。它使用newStdGen抓取该列表的前10个元素,并用randoms g :: [Coin]输出它们。

takeprint类似,但也通过r函数进行抛硬币,该函数返回一对给定的硬币数量(coins )以及processlength cs)的数量;和Heads函数将结果格式化为字符串百分比。

答案 1 :(得分:2)

这是重写的版本,其中删除了一些不必要的内容:

module Coin where

import System.Random

data Coin = Tails | Heads deriving (Eq)

此处定义了Coin数据类型。派生的Eq类型类实例意味着您可以使用==比较两个硬币是否相等,这对于计算您获得了多少Heads很有用。我删除了Show,因为它虽然很简洁,但从未在此代码段中使用。我已出于说明目的删除了EnumBounded

instance Enum Coin where
  toEnum 0 = Tails
  toEnum 1 = Heads
  fromEnum Tails = 0
  fromEnum Heads = 1

在您的代码中,这是通过在派生类型类实例的列表中添加Enum来实现的:这意味着从现在开始,您可以拿任何硬币并将其转换为Int(0或1),则可以使用Int(0或1)并将其转换为Coin。自动推导比较整洁,但这清楚地说明了Enum的含义。

instance Bounded Coin where
  minBound = Tails
  maxBound = Heads

在您的代码中,这是通过将Bounded添加到派生类型类实例的列表来实现的:这意味着“最小” Coin的值为Tails,而“最大”值为“ Coin的值为Heads。除了生成随机硬币外,这可能没有任何意义。

instance Random Coin where
  randomR (coin1, coin2) gen =
    case randomR (fromEnum coin1, fromEnum coin2) gen of
      (x, gen') -> (toEnum x, gen')
  random gen = randomR (minBound, maxBound) gen

在这里,我们说我们可以使用标准随机值生成器库生成随机Coin。根据{{​​3}},最小完整定义randomRrandom,这意味着如果您定义,则可以使用所有随机函数来生成随机硬币只有这两个。 (其余随机函数是根据这两个定义的。)

  • randomR有两件事:范围(coin1, coin2)和随机生成器gen并返回元组(toEnum x, gen'),表示:随机硬币toEnum x以及更新后的随机生成器gen'。随机硬币应在coin1coin2的范围内,这听起来很奇怪,因为只有两个值。 (如果您不希望使用(Tails, Heads)的全部范围,那么您将确切知道要获得哪个硬币。)但是对于整数,在一定范围内生成随机值是有意义的,并且随机数生成器库旨在用于具有任意数量的构造函数的数据类型,而不仅仅是两个。

    通过依赖现有Int的随机数生成器来执行实际的随机生成,方法是说:生成随机Heads / Tails就像生成随机{{1 }(0或1),然后将其映射回硬币。这是Int类型类派上用场的地方。

  • Enum只需要做一件事:随机生成器random。然后,它执行与gen相同的操作,只是它提供了整个范围的值。 randomRminBound是说maxBoundTails的奇特方式,因为the Random type class definition实例提供了最小值和最大值的名称。但是我们可以只写HeadsTails

    使用派生的Heads类并分别写入BoundedminBound的优势在于,您可以使用第三个选项扩展数据类型,而不必更改{{1} }实例-如果您忘记了(不会发出警告),那么随机生成器将永远不会生成所有三个选项。

如果您不希望使用maxBoundRandom类型的类实例,则可以以更简单,更不易维护的方式编写Enum,其中{{1 }}和Bounded发生在Random的定义内,并且该类型的最小和最大界限分别被硬编码为CoinInt

randomR

在这里,我们不依赖TailsHeadsinstance Random Coin where randomR (Tails, Heads) gen = case randomR (0, 1) gen of (0, gen') -> (Tails, gen') (1, gen') -> (Heads, gen') randomR (someCoin, _) gen = (someCoin, gen) random gen = randomR (Tails, Heads) gen toEnum,而我们滥用fromEnum文档中的措辞:如果lo> hi 会发生什么,目前尚无定论,这意味着如果我们没有在minBound间隔内生成随机硬币,那么我们要么在 lo = hi maxBoundrandomR的情况,在这种情况下,我们不需要随机性,因为只有一个值可供借鉴,或者我们处于 lo> hi < / em>,在这种情况下,我们也只返回间隔中的第一个值。 (在使用(Tails, Heads)(Heads, Heads)的代码中,处理无意义的范围的职责只是传递给了随机的(Tails, Tails)数字生成器,它的作用大致相同。)