
Thanks for the replies. This was my solution: module RandomTest ( random_test ) where import Random random_test :: Int -> IO String random_test n = do g <- newStdGen return $ take n (randomRs printable_ascii g) where printable_ascii = ('!','~') The "struggling with the type system" was supposed to be tongue-in-cheek. I trust it, it just takes some time to get used to it. And I appreciate Dan's comments about separating the pure code from the non-pure, but in this case the code was succinct enough that one more layer of functions seemed to do more harm than good when it came to legibility. - Stephen Jon Fairbairn wrote:
Stephen Howard
writes: I am a newcomer doing my obligatory struggling with Haskell's type system,
That's your mistake. Don't struggle with the type system, the type system is your friend; when it tells you you are doing something wrong, it's usually right.
and I've got a nut I've not been able to crack. Given:
import Random
random_test :: Int -> String random_test n = do g <- getStdGen take n (randoms g)::String
My immediate reaction on seeing this is that "do" is for manipulating monads, but the only monad you've indicated in the type is [] (from String = [Char]), and that's probably an accident.
In GHCi:
Prelude> :t Random.getStdGen Random.getStdGen :: IO System.Random.StdGen
What this says is that getStdGen isn't a function, which is to say it doesn't always return the same value (ie it isn't pure -- it wouldn't be much use if it were!). In effect it uses the real world to get a new value, and such things are kept safely in the IO monad. In C, everything is in the IO monad; there's no way of telling from the type whether something is pure or not¹. In Haskell (apart from some ugliness that's supposed to stay hidden) you have to distinguish between pure and impure, and the type checker keeps track of all this for you.
And yet, when I run these lines in GHCI by hand,
The top level of GHCi is IO (which shouldn't come as a surprise!)
things seem to work (though the string is the same set of random characters each time, another bit that I need to solve
That's a somewhat obscure aspect of GHCi, I think, to do with "not reverting top-level expressions". If you do this:
Prelude> :set +r Prelude> g <- Random.getStdGen -- what's the type? 1954047482 7573 Prelude> g <- Random.getStdGen -- what's the type? 1626678354 7697 Prelude>
you see that you get a different StdGen each time.
I'm guessing that randoms is returning an IO type but I'm not sure how to go about extracting the String to return to the calling action. Changing the type signature to Int -> IO String only gives me a different error.
If you do any IO, you've done some IO! So you need to lift out the StdGen and pass that to the functions that you want to use it.
main :: IO() do gen <- getStdGen the_list <- real_programme gen print the_list
You should be able to deduce from the fact that the first argument of real_programme here is of type StdGen (not IO anything) and its result type is [something] that real_programme is pure, and gen has only one value throughout. So if you want more than one random list out of it, you either need to use Random.split or pass more than one gen in.
[1] Were you truly perverse, you could define your own libraries in Haskell so that YourInt = IO Int; YourDouble = IO Double etc, and make appropriate classes in place of Num &c (hiding the proper ones), so that
+ :: YourInt -> YourInt -> YourInt
and so on (you'd have to define your own Bool and if, too, but Haskell can do that). Then the type checker would overlook all sorts of mistakes that it could otherwise have caught.