Thor Correia
Thor Correia

Reputation: 1309

Using System.Random over multiple data types - the Haskell way?

I'm trying to learn Haskell in the most "Haskell way" possible, so I'm trying to avoid things like dos and lets that sort of obscure what's actually going on.

So I have a data type called Org that in is defined as so:

data Org = Org 
    { pos :: Pos2D
    , color :: Color           
    } deriving (Show)

where Color and Pos2D are defined as so:

-- Not actually defined like this, it's Graphics.Gloss.Data.Color
data Color = Color Float Float Float Float
data Pos2D = Pos2D Float Float

Now I've implemented UniformRange for Pos2D and Uniform for Color as so:

instance UniformRange Pos2D
  where
    uniformRM (Pos2D x1 y1, Pos2D x2 y2) g = Pos2D
        <$> uniformRM (x1, x2) g
        <*> uniformRM (y1, y2) g

instance Uniform Color
  where
    uniformM g = G.makeColor
        <$> uniformRM (0.0, 1.0) g
        <*> uniformRM (0.0, 1.0) g
        <*> uniformRM (0.0, 1.0) g
        <*> return 1.0

Given a tuple of Pos2D (representing the corners of the bounds), it will return a random Pos2D distributed uniformly between those. And for Color, it will return a random color with uniform R, G, and B values, but with a fixed alpha = 255 (1.0).

The question is now, how do I make an org?

randomOrg :: RandomGen g => g -> Org
randomOrg = -- ...
  where
    gen = mkStdGen 1337

The issue I'm having is that uniformR and uniformM take a g and return a tuple of (a, g). So I'm not sure what the cleanest, most "Haskelly" way would be to actually chain these calls together to create the Org.

Any thoughts? Thanks,

Edit:

I know that I can do this:

randomOrg :: RandomGen g => g -> Org
randomOrg g = Org pos color
  where
    (pos, g1) = uniformR (Pos2D 0 0, Pos2D 720 480) g
    (color, g2) = uniform g1

But I'm not a fan of that for obvious reasons.

Edit 2:

Ok so after some thinking, I think the function I need might need to look something like this?

pUniform
    :: (Uniform a, RandomGen g)
    => (a -> b, g) -> (g -> (a, g)) -> (b, g)

But I'm not sure.

Edit 3:

Now I'm thinking a functor might be relvant:

(<$>) :: (Functor f) => (a -> b) -> f a -> f b  

So if we take some function (a->b) (which could be for example the Org constructor), and we have some a wrapped in the context f, which would be like (a, g) then we can produce a b wrapped in the context, which would be (b, g). Seems right to me, but I'm not sure how to translate that into actual code.

Upvotes: 2

Views: 386

Answers (1)

bradrn
bradrn

Reputation: 8477

As outlined here, the best way of doing this is by using the new monadic interface:

randomOrg :: StatefulGen g m => g -> m Org
randomOrg g = Org <$> uniformR (Pos2D 0 0, Pos2D 720 480) g <*> uniform g

Which you can then specialise to a pure stateful function:

randomOrg' :: RandomGen g => g -> (Org, g)
randomOrg' g = runStateGen g randomOrg

Though this approach is more general, so you can also run it e.g. in IO using mutable state:

randomOrg'' :: RandomGen g => IOGenM g -> IO Org
randomOrg'' = randomOrg

(Warning: these should work, but I haven’t double-checked it myself; please tell me if you find any lurking errors!)

Upvotes: 4

Related Questions