【问题标题】:Haskell: Random Coin InstanceHaskell:随机硬币实例
【发布时间】:2017-02-25 01:36:15
【问题描述】:

我已经定义了一个Coin 数据类型:

data Coin = H | T
    deriving (Bounded, Eq, Enum, Ord, Show)

鉴于以下框架,我现在必须编写一个 Random Coin 实例:

instance Random Coin where
randomR (l, h) g = undefined
random           = undefined

显然,这个随机实例应该返回 H 或 T。我开始了解 Monad 的工作原理,但是,我对初始随机生成器感到困惑。我知道一个随机生成器返回一个(a, gen),但是我们从哪里得到初始生成器来创建第一个随机硬币?我目前有以下:

instance Random Coin where
randomR (l, h) g = case randomR(l, h) g of
                   (c, g') -> (c, g')
random           = getStdRandom(randomR(minBound :: Coin, maxBound :: Coin))

我对@9​​87654326@ 函数感到特别困惑,因为特定的表达式似乎返回类型 IO Coin。非常感谢任何能启发我的帮助!

【问题讨论】:

  • case foo of (x, y) -> (x, y)case foo of anything -> anything 相同,与foo 相同。因此,您的实例中只剩下randomR (l, h) g = randomR (l, h) g。对我来说,这就像一个非常花哨的无限循环!

标签: haskell random generator


【解决方案1】:

Random 类的重点在于它的方法为您提供了一个生成器,然后要求您生成下一个值和生成器。每当您的类同时派生 EnumBounded 时,实际上都有一个用于生成随机实例的样板方法:

instance Random Coin where
  randomR (lo, hi) g = let (a, g') = randomR (fromEnum lo, fromEnum hi) g in (toEnum a, g')
  random = randomR (minBound, maxBound)

(事实上,already a package 正是为你做这件事。)

【讨论】:

  • 谢谢亚历克。为什么random函数中的randomR (minBound, maxBound)不需要生成器?它的功能需要一对和一个生成器,对吧?
  • @Felix:这是一个 eta-reduction 的例子。 random g = something g 等价于 random = something。同样,\x -> f x 等价于 f,因为 lambda 只是将其参数传递给 f
【解决方案2】:

你定义的函数有类型

randomR :: RandomGen g => (Coin, Coin) -> StdGen -> (Coin, StdGen)
random  :: RandomGen g => StdGen                 -> (Coin, StdGen)

换句话说,你已经得到了一个随机生成器——randomR 的第二个参数和 random 的第一个参数。任何获取系统随机生成器的尝试都会导致类型错误,因为突然间您已将 IO 添加到混合中,而这在这些类型中不会发生。

通常,随机实例将基于整数的函数之一转换为所需的特定类型。例如,我们可以使用

 randomR :: (RandomGen g) => (Integer, Integer) -> g -> (Integer, g)

在 (0,1) 范围内生成一个数字并将 0 转换为 H 并将 1 转换为 T

instance Random Coin where
  randomR (low,high) generator =
    case randomR (toInt low, toInt high) generator of
      (i,g) -> (toCoin i, g)
   where
    toInt :: Coin -> Integer
    toInt H = 0
    toInt T = 1
    toCoin 0 = H
    toCoin 1 = T

我将留下random 供您定义。

顺便说一句,您可能会意识到这与Bool 必须具有的实例基本相同,因此解决您的问题的明智方法也是检查source code for the "random" package 并搜索“instance Random Bool” .

之后,您可以将它与任何随机生成器一起使用,如下所示:

> let gen = mkStdGen 1 in take 10 $ randomRs (H,T) gen
[T,T,T,H,H,T,H,H,H,T]

【讨论】:

  • 我最初提到了一个实际上并没有从模块中导出的函数,对此感到抱歉。
  • 谢谢莎拉。我不确定我是否遗漏了一个非常简单的观点,但由于我们正在实例化“randomR”,我究竟是如何得到一个随机生成器的?这个生成器是否已经在 Random 类中定义了?
  • 不,它只是您定义的函数的一个参数。 randomR (low, high) gen = foo 本质上是在说:给定值(低、高)和 gen,我可以给你 foo。当您实际使用该功能时(即不是在您定义它时),您必须为它提供这样的生成器。
  • @Felix 我添加了一个示例。并修复了一个错误。 ;-)
  • 使用Random Bool 实例而不是Random Int 不是更简洁(更少的部分函数)吗?我在回答中使用后者的唯一原因是因为它扩展到 EnumBounded...
【解决方案3】:

我非常喜欢Alec 的实现,我自己做了。通过分别定义 randomrandomR 函数,它更易于用于多个实例(样板文件更少!)——而且我的钱——也更易于阅读。

import Control.Arrow (first)

boundedRandom :: (Bounded b, Random b, RandomGen g) => g -> (b, g)
boundedRandom = randomR (minBound, maxBound)

enumRandomR :: (Enum e, RandomGen g) => (e,e) -> g -> (e,g)
enumRandomR (min,max) g = toEnum `first` randomR (fromEnum min, fromEnum max) g

instance Random Coin where
        random = boundedRandom
        randomR = enumRandomR

最好的部分是,对于同时实现 EnumBounded 的任何类型,这是即插即用的!

data Suite
        = Spade
        | Heart
        | Club
        | Diamond
  deriving (Bounded, Enum)

instance Random Suite where
        random = boundedRandom
        randomR = enumRandomR

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-21
    • 1970-01-01
    • 2018-07-27
    • 2012-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多