How to pick a random list element in a pure function?
Asked Answered
R

3

22

I want to make a Haskell function that can pick out a random number from a given list. My type signature is:

randomPick :: [a] -> a 

What should I do?

Raeleneraf answered 28/5, 2010 at 2:11 Comment(6)
Why do you want to do this? If you tell us, perhaps we can give you a more useful answer than (the truthful) "you can't".Tracheotomy
here you go: randomPick = (!! 7) (I chose the number 7 totally at random)Baneberry
@yairchu: Good choice, since numbers ending in 7 are indeed very random. See also this possible implementation for a referentially-transparent RNG.Ladin
Well, there is always the infamous "unsafePerformIO", but in this case it really is unsafe; the implementation is perfectly free to cache the first value returned and then replace every subsequent call with the value returned by the first one.Fury
You can't do it in pure functional code. Otherwise, solution already exists hackage.haskell.org/package/random-extras-0.19/docs/…Importunate
@C.A.McCann link changed to scienceblogs.com/cognitivedaily/2007/02/05/… :PBrawley
L
29

Part of the definition of a "pure" function in Haskell is that it is referentially transparent, that is, interchangeable with the result of evaluating it. This implies that the result of evaluating it must be the same every time. Ergo, the function you want isn't possible, I'm afraid. To generate random numbers in Haskell, a function needs to do one of two things:

Take and return a pseudorandom number generator, e.g.:

randomPick :: RNG -> [a] -> (a, RNG)

Or use IO to access randomness from "the outside world":

randomPick :: [a] -> IO a

Both styles are provided by the module System.Random. Also, in the former case, passing the PRNG around can be abstracted out using the State monad, or perhaps a special-purpose Random monad.

Ladin answered 28/5, 2010 at 2:16 Comment(2)
Yes, do see MonadRandom (linked in this answer) - I came here specifically to mention that if it wasn't already here.Wiersma
You can also use QuickCheck's "Gen" monad as a handy ready-baked implementation. Amongst other things it has the required function already implemented.Fury
S
24

What you've described can't be done in pure functional code.

Pure functional code implies that you will get the same output for the same input, every time. Since a randomizing function, by definition, gives you different output for the same input, this is impossible in pure functional code.

Unless you pass around an extra value as explained in @camccann's answer. Technically, it doesn't even have to be as advanced as an RNG, depending on your needs. You could pass around an integer, and multiply it by 10 and subtract 3 (or whatever), then take the modulo of that to find your index. Then your function remains pure, but you do directly control the randomness.

Another option is to use RandomRIO to generate a number in a range, which you can then use to select an index from the list. This will require you to enter the IO monad.

Southwards answered 28/5, 2010 at 2:14 Comment(2)
can I make a other function like is : randomChoices :: [a] -> a randomChoices xs = randomIO (makeIO xs) makeIO :: [a] -> IO [a] makeIO = error " none " randomIO :: IO[a] -> a randomIO = error " none " if so how ?Raeleneraf
No, that's not possible either; while makeIO :: [a] -> IO [a] is just return, there's no function with type IO a -> a—there's no way to leave the IO monad once you've entered it. This is because, as Mark said, Haskell is pure: you can't change the values of variables, and so every time a function is given an input, it must always return the same output. Since IO is a pure way of representing actions which, when taken, might (seem like they) mutate things, it must be un-destructurable, and this is enforced by its definition being hidden and the lack of a function from IO a -> a.Tracheotomy
F
8

If you want to use random number generators in purely functional code but not have to explicitly pass around generator state then you can use state monad (or monad transformer) and hide the plumbing. State monads are still referentially transparent and it's safe & normal to escape a state monad. You could also use the ST monad if you want true local mutable state that is purely functional on the outside.

Here is some useful code I wrote and use sometimes:

rand :: (Random a, RandomGen g, MonadState g m) => a -> a -> m a
rand lo hi = do
    r <- get
    let (val, r') = randomR (lo, hi) r
    put r'
    return val
Fact answered 28/5, 2010 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.