请帮助我理解下面的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
我无法理解{-hi-}Eq, {-/hi-} Show
的含义。
及以下内容,“ of”和下一部分的意义是什么。
case randomR(fromEnum a,fromEnum b)g的 (x,g')->(toEnum x,g')
答案 0 :(得分:6)
Coin
是具有两个构造函数Heads
和Tails
的代数数据类型,表示具有两个值的枚举。它与Bool
(具有相同的结构)是同构的,但是是独特的类型。
deriving (Eq, Show, Enum, Bounded)
自动生成类型类的实现:
Eq
,这是支持测试与==
相等的类型的类型
Show
,用于将值转换为字符串以进行调试
Enum
,用于使用前置函数和后继函数pred
和succ
Bounded
,类型分别为minBound
(此处为Heads
和maxBound
(Tails
)
{-
…-}
只是注释,编译器会忽略它;看来作者打算使用某种无法正常工作的非Haskell格式表示法。
instance Random Coin where
开始为Random
类型实现Coin
类型类,从而实现硬币的随机生成。它具有两种方法的实现:
randomR (a, b) g
描述了如何使用随机生成器a
生成b
到g
范围内的随机值。该实现调用randomR
使用a
类在参数b
和Enum
的范围内生成随机 integer 。如果a
为Heads
,则fromEnum a
为0
;如果b
是Tails
,则fromEnum b
是1
。 case
…of
表示法对该函数的结果执行 pattern match ,获得一对随机值x
和更新后的随机生成器{{ 1}}(读作“ G素数”)。然后,它使用g'
将x
从整数转换回Coin
,并返回硬币值和更新的生成器。
toEnum
描述了如何仅从生成器生成随机硬币而不将范围作为输入。它使用给定的生成器random
在minBound
(Heads
)和maxBound
(Tails
)之间产生硬币抛掷。
g
是一个coins
动作,它使用IO
创建一个新的标准随机生成器,然后使用{{1}从该生成器生成一个无限的随机抛硬币列表(流) }。它使用newStdGen
抓取该列表的前10个元素,并用randoms g :: [Coin]
输出它们。
take
与print
类似,但也通过r
函数进行抛硬币,该函数返回一对给定的硬币数量(coins
)以及process
(length cs
)的数量;和Heads
函数将结果格式化为字符串百分比。
答案 1 :(得分:2)
这是重写的版本,其中删除了一些不必要的内容:
module Coin where
import System.Random
data Coin = Tails | Heads deriving (Eq)
此处定义了Coin
数据类型。派生的Eq
类型类实例意味着您可以使用==
比较两个硬币是否相等,这对于计算您获得了多少Heads
很有用。我删除了Show
,因为它虽然很简洁,但从未在此代码段中使用。我已出于说明目的删除了Enum
和Bounded
。
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}},最小完整定义为randomR
和random
,这意味着如果您定义,则可以使用所有随机函数来生成随机硬币只有这两个。 (其余随机函数是根据这两个定义的。)
randomR
有两件事:范围(coin1, coin2)
和随机生成器gen
并返回元组(toEnum x, gen')
,表示:随机硬币toEnum x
以及更新后的随机生成器gen'
。随机硬币应在coin1
和coin2
的范围内,这听起来很奇怪,因为只有两个值。 (如果您不希望使用(Tails, Heads)
的全部范围,那么您将确切知道要获得哪个硬币。)但是对于整数,在一定范围内生成随机值是有意义的,并且随机数生成器库旨在用于具有任意数量的构造函数的数据类型,而不仅仅是两个。
通过依赖现有Int
的随机数生成器来执行实际的随机生成,方法是说:生成随机Heads
/ Tails
就像生成随机{{1 }(0或1),然后将其映射回硬币。这是Int
类型类派上用场的地方。
Enum
只需要做一件事:随机生成器random
。然后,它执行与gen
相同的操作,只是它提供了整个范围的值。 randomR
和minBound
是说maxBound
和Tails
的奇特方式,因为the Random
type class definition实例提供了最小值和最大值的名称。但是我们可以只写Heads
和Tails
。
使用派生的Heads
类并分别写入Bounded
和minBound
的优势在于,您可以使用第三个选项扩展数据类型,而不必更改{{1} }实例-如果您忘记了(不会发出警告),那么随机生成器将永远不会生成所有三个选项。
如果您不希望使用maxBound
和Random
类型的类实例,则可以以更简单,更不易维护的方式编写Enum
,其中{{1 }}和Bounded
发生在Random
的定义内,并且该类型的最小和最大界限分别被硬编码为Coin
和Int
:
randomR
在这里,我们不依赖Tails
,Heads
,instance 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 maxBound
或randomR
的情况,在这种情况下,我们不需要随机性,因为只有一个值可供借鉴,或者我们处于 lo> hi < / em>,在这种情况下,我们也只返回间隔中的第一个值。 (在使用(Tails, Heads)
和(Heads, Heads)
的代码中,处理无意义的范围的职责只是传递给了随机的(Tails, Tails)
数字生成器,它的作用大致相同。)