歧义类型错误和同一函数中的多个不同随机值

时间:2018-10-21 20:37:05

标签: haskell random types io

我需要在不同范围内的多个随机数(可以通过将随机数生成器的结果乘以一个常数来避免)。

我正在Haskell中制作一个小行星克隆,我想产生随机产生的敌人。我想让它们以相同的速度在屏幕边缘生成(所以速度矢量的范数相等)。

其他信息:当实体撞击屏幕边缘时,它再次出现在另一侧,这就是为什么我选择只让敌人在屏幕四个边缘中的两个边缘产生,并让速度真正决定您首先在哪里看到它们出现。

我看着System.Random遇到了困难。我认为我需要五个随机数:

  1. 一个随机数,以确定在这个帧上怪物是否应该 产生。
  2. 随机x位置或随机y位置(产生在边缘
  3. 确定敌人在屏幕哪一侧产生的随机数
  4. x速度的随机数
  5. 随机数(1或-1)以创建具有设定范数的速度矢量。

在游戏开始时,我会生成一个新的StdGen,然后每帧进行一次。

我想到但认为是不好的解决方案:不用每次通过方法传递生成器,而是每次使用newStdGen创建一个新的生成器。

我还考虑过为需要的所有随机数调用newRand函数。但是,如果我有一个函数需要在相同范围内的两个随机数,则这两个随机数将是相同的,因为相同的输入在Haskell中总是给出相同的输出。

问题:除了对newRand函数(也用于更新每帧生成器)的调用之外,所有随机调用中的类型变量都不明确,因为Haskell不知道要使用哪种数字类型。

示例错误:

src\Controller.hs:45:56: error:
    * Ambiguous type variable `a0' arising from the literal `0.0'
      prevents the constraint `(Fractional a0)' from being solved.
      Relevant bindings include
        axis :: a0 (bound at src\Controller.hs:45:25)
      Probable fix: use a type annotation to specify what `a0' should be.
      These potential instances exist:
        instance HasResolution a => Fractional (Fixed a)
          -- Defined in `Data.Fixed'
        instance Fractional Double -- Defined in `GHC.Float'
        instance Fractional Float -- Defined in `GHC.Float'
        ...plus three instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the expression: 0.0
      In the first argument of `randomR', namely `(0.0, 1.0)'
      In the second argument of `($)', namely `randomR (0.0, 1.0) gen'
   |
45 |                         axis = signum $ fst $ randomR (0.0, 1.0) gen
   |                                                        ^^^

我的代码:

newRand :: StdGen -> (Float, Float) -> (Float, StdGen)
newRand gen (a, b) = randomR (a, b) gen

genEnemies :: StdGen -> Float -> [Moving Enemy]
genEnemies gen time | rand > 995 = [Moving (x, y) (vx, vy) defaultEnemy]
                    | otherwise  = []
                  where rand = fst $ newRand (0, 1000) gen
                        x | axis < 0.5  = fst $ randomR (0.0, width) gen
                          | otherwise   = 0
                        y | axis >= 0.5 = fst $ randomR (0.0, height) gen
                          | otherwise   = 0
                        axis = signum $ fst $ randomR (0.0, 1.0) gen
                        vx   = fst $ randomR (-20.0, 20.0) gen
                        vy   = sgn * sqrt (400 - vx*vx)                  
                        sgn  = (signum $ fst $ randomR (-1.0, 1.0) gen)

1 个答案:

答案 0 :(得分:3)

想要在Haskell中生成多个随机数的通常模式如下:

foo :: StdGen -> (StdGen, (Int, Int, Int))
foo g0 = let
    (val1, g1) = randomR (0, 10) g0
    (val2, g2) = randomR (0, 10) g1
    (val3, g3) = randomR (0, 10) g2
    in (g3, (val1, val2, val3))

例如,在ghci中:

System.Random> foo (mkStdGen 0)
(1346387765 2103410263,(7,10,2))

您会看到它返回了三个不同的数字,这与您每次在代码中用randomR调用g0时得到的数字不同。

希望您能抓住这种模式:以所需的范围和randomR调用StdGen;使用返回的值作为随机数,并在下次调用StdGen时使用返回的randomR作为输入。从您的随机函数返回更新后的StdGen也很重要,这样您就可以在以后的调用中继续使用该模式。

稍后,您可以查看monad,尤其是RandT,它可以抽象出将最新更新的StdGen馈入下一次对randomR的调用的过程。 RandT样式的示例如下:

foo' :: MonadRandom m => m (Int, Int, Int)
foo' = do
    val1 <- getRandomR (0, 10)
    val2 <- getRandomR (0, 10)
    val3 <- getRandomR (0, 10)
    return (val1, val2, val3)

...但是现在,坚持基础知识。一旦您完全理解它们,当您实现(或重用)允许您执行此类操作的抽象时,魔术的感觉就会大大减少。