tags:

views:

92

answers:

3

Hi all, Im pretty new to Haskell. I have a datatype:

data Sentence= Prop Int
          | No Sentence
          | And [Sentence]
          | Or [Sentence]
          deriving Eq

I already wrote a Show instance for it

However, whether it makes sense or not, I would like to be able to generate a random Sentence. How can i accomplish this in Haskell?

+5  A: 

Random number generation is a typical example of an "impure" operation, since calling a random generator twice will of course yield different results - Haskell's nature disallows this.

Therefore, you need to use a so called monad Gen a that represents a random generator, that, when run, yields a value of type a.

Fortunately, you can combine these generators in a quite nice syntax ...

So, you just need some library that implements such a generator - and here we go.

randomNo   = No <$> randomSentence
randomProp = Prop <$> choose (1, 10) 
[...]

randomSentence = oneOf [randomNo, randomProp, ...]
Dario
Thanks for the superfast response, but what do you mean by <$> ?
SirLenz0rlot
Ah, it's from `Control.Applicative` - just some simplified syntax that means you can apply a function to a random value.
Dario
It's just `fmap`
FUZxxl
ah ok, however, when i test your code, Haskell says: Not in scope: `oneOf'But it should be in Quickcheck right?
SirLenz0rlot
Yep, you'll need *Test.QuickCheck* and *System.Random* (*Control.Applicative* and *Control.Monad* are also quite nice for some extra functionality/syntax)
Dario
Strange, it recognises the imports, but still it doesnt recognise oneOf somehow
SirLenz0rlot
Yes, indeed it should be there.
FUZxxl
Ah, its oneof, not oneOf :P how stupid of me...im puzzling how to get the Gen Sentence printed
SirLenz0rlot
See the quickcheck documentation: http://hackage.haskell.org/packages/archive/QuickCheck/2.1.0.3/doc/html/Test-QuickCheck-Gen.html. Call `unGen` to unpack the generator function then run it. The first argument is a `StdGen` from `System.Random`. The next argument is "size" (which will control how big your results are).
sclv
If you use QC2 then there should be a `sample` function so you don't need to worry about `StdGen` or `unGen` at all (if you don't want to).
TomMD
+1  A: 

The easiest way is to use the module System.Random, it's in the random package, so you probably have to install it first.

This module defines to typeclasses:

class RandomGen g where
  next :: g -> (Int,g)
  -- ...

class Random r where
  random ::  RandomGen g => g -> (a,g)
  randomR :: RandomGen g => (r,r) -> g -> (a, g)

The typeclass, you have to implement is Random, specific the first function (as the second makes no sense, you can just implement like randomR = const random. What does random? You get a random generator as input, you have to generate what you need for it, and give the new generator back.

To generate your random values, you could either use the State monad, or something like this:

random g = (myResult,gn) where
  (random1,g1) = next g
  (random2,g2) = next g2
  -- ...

You can then use the systems random generator by this function:

randomIO :: Random r => IO r

It is predefined and yields a different value each call.

However, finally you have to decide yourself how to define your Random instance.

FUZxxl
+2  A: 

My favorite method is to use the MonadRandom package. Although it boils down to the same thing as passing some RandomGen around, it does it for you and ensures that you don't mess up in the process (such as passing an already-used generator). Further, Rand StdGen a has the nice interpretation of "a probability distribution of as".

For your example, it might look like this:

-- a type for probability distributions
type Dist = Rand StdGen

-- pick uniformly between a list of values
uniform :: [a] -> Dist a
uniform xs = do
    ix <- getRandomR (0, length xs - 1)
    return (xs !! ix)

-- return a list of elements generated by the given distribution
randList :: Int -> Dist a -> Dist [a]
randList maxElems dist = do
    elems <- getRandomR (0, maxElems)
    sequence (replicate elems dist)

-- return a probability distribution of sentences
randSentence :: Dist Sentence
randSentence = do
    -- choose one of these four distributions by a uniform distribution
    -- (uniform [...] returns a distribution of distributions)
    dist <- uniform [
        Prop <$> getRandom,
        No <$> randSentence,
        And <$> randList 5 randSentence,
        Or <$> randList 5 randSentence ]
    -- and sample the one we chose
    dist

Note the magic number 5 up there. That's so we don't get 2 billion element lists. You might want to tweak the distribution of the number of terms in your randomly generated lists.

And to run it you can use evalRandIO or many other things, say like:

main = print =<< evalRandIO randSentence
luqui
Thanks!I found that the Monad.Random library is not included in the latest windows package. perhaps for a reason? Since when I copy-paste the source from it (http://www.haskell.org/haskellwiki/NewMonads/MonadRandom) into an own file, it gives me errors of which i have no idea how to solve it:Illegal instance declaration for `MonadState s (RandT g m)'
SirLenz0rlot
You need to install via cabal, not copy and paste. I assume you have Haskell Platform installed. Then exectute `cabal install MonadRandom` at the command prompt. You may need to `cabal update` first.
luqui