If we rewrite slightly your randomNext
function using more conventional notations and shorter lines, it gives something like this:
import System.Random
type Point = (Float, Float)
nextRandoms1 :: Int -> [Point] -> [Point]
nextRandoms1 seed origins =
let add2d = (\(x1,y1) (x2,y2) -> (x1+x2, y1+y2))
count = length origins
range = (-5::Float, 5)
xs = take count $ randomRs range (mkStdGen (seed+0))
ys = take count $ randomRs range (mkStdGen (seed+1))
in
zipWith add2d origins (zip xs ys)
As you have noted, the function does not return anything to allow for the generation of more random values.
More subtly, it uses 2 distinct random series with adjacent seed values. But the library does not offer any guarantee that these 2 series are uncorrelated. Indeed, some random number generators use their seed as just an offset into a shared very large pseudo-random sequence.
Secondarily, it deals with both generating the position increments and adding them to the initial positions.
To avoid these problems, we could start with a modified function which follows the common convention of taking an initial random generator state, and returning an updated state as part of the result:
randomPointUpdates :: StdGen -> (Float, Float) -> Int -> ([Point], StdGen)
randomPointUpdates gen0 range count =
if (count <= 0)
then ([], gen0) -- generator unaltered
else
let (dx, gen1) = randomR range gen0
(dy, gen2) = randomR range gen1
point = (dx, dy)
(rest, gen) = randomPointUpdates gen2 range (count-1)
in
(point : rest, gen)
This randomPointUpdates
function uses recursion on the number of points. It just generates 2D position increments and does not deal with addition at all.
On top of this function, we can now write another one that does deal with addition. As the range is left hardwired, it takes just two arguments: the initial generator state, and the list of initial point positions:
nextRandoms :: StdGen -> [Point] -> ([Point], StdGen)
nextRandoms gen0 origins =
let add2d = (\(x1,y1) (x2,y2) -> (x1+x2, y1+y2))
count = length origins
range = (-5::Float, 5)
(changes, gen1) = randomPointUpdates gen0 range count
points = zipWith add2d origins changes
in
(points, gen1)
We can test that second function using the ghci
interpreter:
λ>
λ> :load q66762139.hs
Ok, one module loaded.
λ>
λ> origins = [(1,2),(2,5),(4,7),(3,3)] :: [Point]
λ> gen0 = mkStdGen 4243
λ>
λ> fst $ nextRandoms gen0 origins
[(3.8172607,-0.54611135),(4.0293427,6.095909),(-0.6763873,6.4596577),(3.042204,-1.2375655)]
λ>
Next, we can use that to write a function that provides an unlimited supply of updated position sets, again using recursion:
randomPointSets :: StdGen -> [Point] -> [[Point]]
randomPointSets gen0 origins =
let (pts1, gen1) = nextRandoms gen0 origins
in pts1 : (randomPointSets gen1 pts1)
Note that the pts1 :
code bit in the last line is what “remembers” the previous position set, so to speak.
Instead of recursion, we could also have uses here the unfoldr :: (s -> Maybe (a, s)) -> s -> [a]
library function, with s
being the state of the generator.
Test program:
printAsLines :: Show α => [α] -> IO ()
printAsLines xs = mapM_ (putStrLn . show) xs
main = do
let seed = 4243
gen0 = mkStdGen seed
origins = [(1,2),(2,5),(4,7),(3,3)] :: [Point]
allPointSets = randomPointSets gen0 origins -- unlimited supply
somePointSets = take 5 allPointSets
putStrLn $ show origins
printAsLines somePointSets
Test program output:
$ q66762139.x
[(1.0,2.0),(2.0,5.0),(4.0,7.0),(3.0,3.0)]
[(3.8172607,-0.54611135),(4.0293427,6.095909),(-0.6763873,6.4596577),(3.042204,-1.2375655)]
[(7.1006527,1.5599048),(8.395166,3.1540604),(-2.486746,9.749242),(2.2286167,-1.868607)]
[(11.424954,-0.13780117),(6.5587683,2.593749),(-2.8453062,7.9606133),(2.1931071,-4.915463)]
[(13.615167,-1.636116),(10.159166,1.8223867),(1.733639,6.011344),(6.2104306,-3.4672318)]
[(16.450119,-2.8003001),(12.556836,5.0577183),(2.8106451e-2,4.4519606),(2.2063198,-0.5508909)]
$
Side note:
Here, we have used manual chaining of the generator state. For more complex usage of pseudo-random numbers, this technique can become too cumbersome. If so, more powerful monadic notations from the Control.Monad.Random package can be used instead.