Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Random cannot construct the infinite type: a1 = IO a1

Tags:

io

random

haskell

I want to generate a list with 26 random integers which sum is 301 with Haskell. I write the following:

import System.Random

f 1 sum = [sum]
f n sum = m : (f (n-1) (sum-m))
    where m = randomRIO (0,sum)

but it can't be compiled! I am confused with IO!

Occurs check: cannot construct the infinite type: a1 = IO a1
In the first argument of `(:)', namely `m'
In the expression: m : (f (n - 1) (sum - m))
In an equation for `f':
    f n sum
      = m : (f (n - 1) (sum - m))
      where
          m = randomRIO (0, sum)
like image 485
speeding Avatar asked Dec 01 '22 21:12

speeding


2 Answers

The error message is somewhat confusing in this case, but the punchline is that you need to work in the IO monad, since it's using randomRIO which is in IO, and there is (by design) no way to run IO code from non-IO code.

f 1 sum = return [sum]
f n sum = do
  x  <- randomRIO (0, sum)
  xs <- f (n - 1) (sum - x)
  return (x : xs)
like image 157
hammar Avatar answered Dec 04 '22 10:12

hammar


As others have pointed out, your algorithm will not give uniformly-distributed output.

An easy way to get uniform output is:

  • Generate n-1 random numbers in the range from 0 to sum (inclusive)
  • Insert 0 and sum into the list of random numbers
  • Sort the resulting list
  • Return the list of differences between consecutive values in the sorted list

Example:

  • Suppose we want four integers with a sum of 100, we request three random values from the RNG and it gives us [72,33,43]
  • We insert 0 and 100 and sort the list, giving [0,33,43,72,100]
  • We compute the differences [33-0, 43-33, 72-43, 100-72]
  • The result would be [33,10,29,28]

In Haskell:

randomsWithSum :: (Num n, Ord n, Random n) => Int -> n -> IO [n]
randomsWithSum len sum =
    do b <- sequence $ take (len-1) $ repeat $ randomRIO (0,sum)
       let sb = sort (sum:b) in
           return $ zipWith (-) sb (0:sb)

For your example you would call this as randomsWithSum 26 (301::Int)

The same applies to floating-point types, e.g. randomsWithSum 4 (1::Double)


Edit Swapped the arguments, so that 26 `randomsWithSum` 301 does what its name suggests.

like image 43
finnw Avatar answered Dec 04 '22 10:12

finnw