Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
350 views
in Technique[技术] by (71.8m points)

haskell - Ambiguous type error & Multiple different random values in the same function

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

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.

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

  1. A random number to determine if at this frame a monster should spawn.
  2. Random x position or random y position (spawns at the edge
  3. Random number to determine on which side of the screen an enemy spawns
  4. Random number for x velocity
  5. Random number (1 or -1) to create a velocity vector with a set norm.

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

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

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.

Problem: Ambiguous type variables at all the random calls except for the call to the newRand function (that is also used to update the generator each frame), because Haskell doesn't know which type of number to use.

Sample error:

srcController.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 srcController.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
   |                                                        ^^^

My code:

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)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

For example, in ghci:

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

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.

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.

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...