类型不明确的错误&同一函数中有多个不同的随机值 [英] Ambiguous type error & Multiple different random values in the same function

查看:47
本文介绍了类型不明确的错误&同一函数中有多个不同的随机值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need multiple random numbers in different ranges (which could be avoided by multiplying the result of the random number generator with a constant).

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

I'm making an Asteroid clone in Haskell, and I want to generate randomly spawned enemies. I want to let them spawn at the edge of the screen with the same velocity (so norm of velocity vector equal).

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

Additional info: When an entity hits the edge of the screen it appears again at the other side, this is why I chose to only let enemies spawn at two of four edges of the screen and made the velocity really determine where you first see them appear.

我看着 System.Random 遇到了麻烦.我认为我需要五个随机数:

I looked at System.Random and ran into difficulties. I figured I needed five random numbers:

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

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

At the start of the game I generate a new StdGen and after that I do this every frame.

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

Solutions I thought of but thought were bad practice: Instead of passing down generators through methods, create a new generator every time using newStdGen.

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

I also thought of calling the newRand function for all the random numbers I need. But if I had a function that required two random numbers in the same range, the two random numbers would be identical, since identical input always gives identical output in 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)

推荐答案

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

The usual pattern when you want to generate multiple random numbers in Haskell goes like this:

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))

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

You can see it returned three different numbers -- unlike what you would get if you called randomR with g0 each time as you did in your code.

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

Hopefully you catch the pattern: call randomR with the range you want and a StdGen; use the value it returned as your random piece and the StdGen it returned as the input next time you call randomR. It's also important that you return the updated StdGen from your random function, so that you can continue the pattern in later calls.

稍后您可以查看monad,特别是 RandT ,它可以抽象出将最新更新的 StdGen 输入到下一个对 randomR . RandT 样式的示例如下所示:

Later you can look into monads, specifically RandT, which can abstract away the process of feeding the most recently updated StdGen into the next call to randomR. A sample of the RandT style looks like this:

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)

...但是现在,坚持基础知识.一旦您对它们有充分的了解,当您实现(或重用)让您执行此类操作的抽象时,魔术的感觉就会大大减少.

...but for now, stick with the basics. Once you understand them thoroughly it will feel much less magical when you implement (or reuse) the abstractions that let you do that kind of thing.

这篇关于类型不明确的错误&amp;同一函数中有多个不同的随机值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆