Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A list of random doubles within defined range in Haskell?

Tags:

random

haskell

How can I make a list of random numbers of type 'Double', that fit within a defined range? Info on this matter for a newbie like me is a little bit confusing. Trying something like:

randomlist :: Int -> Int -> [IO Double]
randomlist a b = do
  g <- newStdGen
  return (randomRs (a,b) g)

fails, with error:

Couldn't match expected type `[t0]' with actual type `IO StdGen'

Could you point to mistakes in my code?

like image 403
Arnthor Avatar asked Jul 14 '11 20:07

Arnthor


2 Answers

You almost have it. You have two problems. The main problem is the [IO Double] part of your type signature; this says you'll be returning a list of IO actions, each of which can produce a double. Instead, you want to return an IO [Double]—an IO action which, when run, produces an infinite list of doubles. If you just change that, you're almost done; the remaining issue is that you have a and b as Ints, but return Doubles. If you want to return doubles, your bounds need to be doubles, and similarly for integers. (To convert Ints to Doubles, you can use fromIntegral; to go the other way, you can use round.) Thus, to get your code working, all you need to change is the type signature:

randomlist :: Double -> Double -> IO [Double]
randomlist a b = do
  g <- newStdGen
  return (randomRs (a,b) g)

And in fact, if you'd left off the type signature, everything would have been fine; GHC would have inferred the more general type signature Random a => a -> a -> IO [a]. In other words, your function works with any data type you can generate random members of.

You can also simplify your code slightly. The following, for instance, is equivalent:

randomlist :: Random a => a -> a -> IO [a]
randomlist a b = fmap (randomRs (a,b)) newStdGen

The fmap :: Functor f => (a -> b) -> f a -> f b function allows you to apply an ordinary function inside a functor. What's a functor? Roughly speaking, it's some sort of container; type functions such as [], (r ->), and IO are examples.1 This is exactly what you want; randomRs (a,b) has type (Random a, RandomGen g) => g -> [a], and you instead need to give it something of type IO StdGen, getting a Random a => IO [a] back.

There's one more way you can make this nicer (and this is the way I'd write it). If you import Control.Applicative, you end up with

import Control.Applicative
randomlist :: Random a => a -> a -> IO [a]
randomlist a b = randomRs (a,b) <$> newStdGen

<$> is a synonym for fmap; it looks like $, ordinary application, because they're almost the same. <$> just lifts you into a functor (here, IO).


1: Don't worry if this isn't perfectly clear; you can get away with using this stuff without understanding it in full detail, which will eventually lead to understanding.

like image 71
Antal Spector-Zabusky Avatar answered Dec 21 '22 09:12

Antal Spector-Zabusky


The main mistake is in your type signature. Removing it and asking ghci what the inferred type is gives this:

*Main> :t randomlist
randomlist :: Random a => a -> a -> IO [a]

Of course, you may constrain this to the type Double -> Double -> IO [Double] if you wish, and you can add some calls to fromIntegral if you want to restrict further to integer bounds:

randomlist :: Int -> Int -> IO [Double]
randomlist a b = do
    g <- newStdGen
    return (randomRs (fromIntegral a, fromIntegral b) g)

Note the difference between the type [IO Double] and IO [Double]. The former is a list of computations returning Double, while the latter is a single computation returning a list of Double, which is what you want in this case.

The error message may be a bit cryptic, but it's basically telling you that because newStdGen has the type IO StdGen, the bind <- is only allowed when the type of the do-expression is an IO something, whereas your type signature says the type should be [something].

like image 42
hammar Avatar answered Dec 21 '22 08:12

hammar