Randomness in a nested pure function - haskell

I want to provide a function that replaces each occurrence of # in a string with a different random number. In a non-pure language, it's trivial. However, how should it be designed in a pure language? I don't want to use unsafePerformIO, as it rather looks like a hack and not a proper design.
Should this function require a random generator as one of its parameters? And if so, would that generator have to be passed through the whole stack of invocations? Are there other possible approaches? Should I use the State monad, here? I would appreciate a toy example demonstrating a viable approach...

You would, in fact, use a variant of the state monad to pass the random generator around behind the scenes. The Rand type in Control.Monad.Random helps with this. The API is a bit confusing, but more because it's polymorphic over the type of random generator you use than because it has to be functional. This extra bit of scaffolding is useful, however, because you can easily reuse your existing code with different random generators which lets you test different algorithms as well as explicitly controlling whether the generator is deterministic (good for testing) or seeded with outside data (in IO).
Here's a simple example of Rand in action. The RandomGen g => in the type signature tells us that we can use any type of random generator for it. We have to explicitly annotate n as an Int because otherwise GHC only knows that it has to be some numeric type that can be generated and turned into a string, which can be one of multiple possible options (like Double).
randomReplace :: RandomGen g => String -> Rand g String
randomReplace = foldM go ""
where go str '#' = do
n :: Int <- getRandomR (0, 10)
return (str ++ show n)
go str chr = return $ str ++ [chr]
To run this, we need to get a random generator from somewhere and pass it into evalRand. The easiest way to do this is to get the global system generator which we can do in IO:
main :: IO ()
main = do gen <- getStdGen
print $ evalRand (randomReplace "ab#c#") gen
This is such a common pattern that the library provides an evalRandIO function which does it for you:
main :: IO ()
main = do res <- evalRandIO $ randomReplace "ab#c#"
print res
In the end, the code is a bit more explicit about having a random generator and passing it around, but it's still reasonably easy to follow. For more involved code, you could also use RandT, which allows you to extend other monads (like IO) with the ability to generate random values, letting you relegate all the plumbing and setup to one part of your code.

It's just a monadic mapping
import Control.Applicative
import Control.Monad.Random
import Data.Char
randomReplace :: RandomGen g => String -> Rand g String
randomReplace = mapM f where
f '#' = intToDigit <$> getRandomR (0, 10)
f c = return c
main = evalRandIO (randomReplace "#abc#def#") >>= print

Related

How can I generate different random values in Haskell?

Suppose that I have a list like this:
let list = ["random", "foo", "random", "bar", "random", "boo"]
I want to iterate over a list and map all "random" elements to different random strings:
let newList = fmap randomize list
print newList
-- ["dasidias", "foo", "gasekir", "bar", "nabblip", "boo"]
My randomize function looks like this:
randomize :: String -> String
randomize str =
case str of
"random" -> randStr
_ -> str
where
randStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen
But I get the same random string for every "random" element:
["abshasb", "foo", "abshasb", "bar", "abshasb", "boo"]
I can't figure out why is this happening and how to get a different random value for each occurrence of "random".
There are two problems with your code:
You are calling unsafePerformIO, but explicitly violating the contract of that function. It is on you to prove that the thing you provide to unsafePerformIO is actually pure, and the compiler is within its rights to act as if that's the case, and here it is definitely not.
You are not carefully tracking the updated random number generator state after using it. Indeed, it is not possible to do this correctly with randomRs; if you use randomRs, then to a first approximation, that must be the last randomness your program needs.
The simplest fix to both of these is to admit that you really, truly are doing IO. So:
import Control.Monad
import System.Random
randomize :: String -> IO String
randomize "random" = replicateM 10 (randomRIO ('a', 'z'))
randomize other = pure other
Try it out in ghci:
> traverse randomize ["random", "foo", "random", "bar", "random", "boo"]
["xytuowzanb","foo","lzhasynexf","bar","dceuvoxkyh","boo"]
There is no call to unsafePerformIO, and so no proof burden to shirk; and randomRIO tracks the updated generator state for you in a hidden IORef, and so you correctly continue advancing it on each call.
How not to involve IO in random number generation:
This question has received excellent answers. However, it might leave some readers under the impression that pseudo-random number generation (PRNG) within Haskell is necessarily linked to IO.
Well, it's not. It is just that in Haskell, the default random number generator happens to be "hosted" in the IO type. But this is by choice, not by necessity.
For reference, here is a recent review paper on the subject of PRNGs.
PRNGs are deterministic mathematical automata. They do not involve IO. Using PRNGs in Haskell does not need to involve the IO type. At the bottom of this answer, I provide code that solves the problem at hand without involving the IO type, except for printing the result.
The Haskell libraries provide functions such as mkStdGen that take an integer seed and return a pseudo-random number generator, that is an object of the RandomGen class, whose state is dependent on the value of seed. Note that there is nothing magic about mkStdGen. If for some reason you do not like it, there are alternatives, such as mkTFGen which is based on the Threefish block cipher.
Now, pseudo-random number generation is not managed in the same way in imperative languages such as C++ and in Haskell. In C++, you would extract a random value like this: rval = rng.nextVal();. On top of just returning the value, calling nextVal() has the side effect of altering the state of the rng object, ensuring that next time it will return a different random number.
But in Haskell, functions have no side effects. So you need to have something like this:
(rval, rng2) = nextVal rng1
That is, the evaluation function needs to return both the pseudo-random value and the updated state of the generator. A minor consequence is that, if the state is large (such as for the common Mersenne Twister generator), Haskell might need a bit more memory than C++.
So, we expect that solving the problem at hand, that is randomly transforming a list of strings, will involve a function with the following type signature: RandomGen tg => [String] -> tg -> ([String], tg).
For illustration purposes, let's get a generator and use it to generate a couple of "random" integers between 0 and 100. For this, we need the randomR function:
$ ghci
Prelude> import System.Random
Prelude System.Random> :t randomR
randomR :: (RandomGen g, Random a) => (a, a) -> g -> (a, g)
Prelude System.Random>
Prelude System.Random> let rng1 = mkStdGen 544
Prelude System.Random> let (v, rng2) = randomR (0,100) rng1
Prelude System.Random> v
23
Prelude System.Random> let (v, rng2) = randomR (0,100) rng1
Prelude System.Random> v
23
Prelude System.Random> let (w, rng3) = randomR (0,100) rng2
Prelude System.Random> w
61
Prelude System.Random>
Note that above, when we forget to feed the updated state of the generator, rng2, into the next computation, we get the same "random" number 23 a second time. This is a very common mistake and a very common complaint. Function randomR is a pure Haskell function that does not involve IO. Hence it has referential transparency, that is when given the same arguments, it returns the same output value.
A possible way to deal with this situation is to pass the updated state around manually within the source code. This is cumbersome and error prone, but can be managed. That gives this style of code:
-- stateful map of randomize function for a list of strings:
fmapRandomize :: RandomGen tg => [String] -> tg -> ([String], tg)
fmapRandomize [] rng = ([], rng)
fmapRandomize(str:rest) rng = let (str1, rng1) = randomize str rng
(rest1, rng2) = fmapRandomize rest rng1
in (str1:rest1, rng2)
Thankfully, there is a better way, which involves the runRand function or its evalRand sibling. Function runRand takes a monadic computation plus (an initial state of) a generator. It returns the pseudo-random value and the updated state of the generator. It is much easier to write the code for monadic computations than to pass the generator state manually around.
This is a possible way to solve the random string substitution problem from the question text:
import System.Random
import Control.Monad.Random
-- generic monadic computation to get a sequence of "count" random items:
mkRandSeqM :: (RandomGen tg, Random tv) => (tv,tv) -> Int -> Rand tg [tv]
mkRandSeqM range count = sequence (replicate count (getRandomR range))
-- monadic computation to get our sort of random string:
mkRandStrM :: RandomGen tg => Rand tg String
mkRandStrM = mkRandSeqM ('a', 'z') 10
-- monadic single string transformation:
randomizeM :: RandomGen tg => String -> Rand tg String
randomizeM str = if (str == "random") then mkRandStrM else (pure str)
-- monadic list-of-strings transformation:
mapRandomizeM :: RandomGen tg => [String] -> Rand tg [String]
mapRandomizeM = mapM randomizeM
-- non-monadic function returning the altered string list and generator:
mapRandomize :: RandomGen tg => [String] -> tg -> ([String], tg)
mapRandomize lstr rng = runRand (mapRandomizeM lstr) rng
main = do
let inpList = ["random", "foo", "random", "bar", "random", "boo", "qux"]
-- get a random number generator:
let mySeed = 54321
let rng1 = mkStdGen mySeed
-- execute the string substitutions:
let (outList, rng2) = mapRandomize inpList rng1
-- display results:
putStrLn $ "inpList = " ++ (show inpList)
putStrLn $ "outList = " ++ (show outList)
Note that above, RandomGen is the class of the generator, while Random is just the class of the generated value.
Program output:
$ random1.x
inpList = ["random","foo","random","bar","random","boo","qux"]
outList = ["gahuwkxant","foo","swuxjgapni","bar","zdjqwgpgqa","boo","qux"]
$
The fundamental problem with your approach is that Haskell is a pure language, and you're trying to use it as if its not. In fact this isn't the only fundamental misunderstanding of the language that your code displays.
In your randomise function:
randomize :: String -> String
randomize str =
case str of
"random" -> randStr
_ -> str
where
randStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen
you clearly intend that randStr takes a different value each time it is used. But in Haskell, when you use the = sign, you are not "assigning a value to a variable", as would be the case in an imperative language. You are saying that these two values are equal. Since all "variables" in Haskell are actually "constant" and immutable, the compiler is perfectly entitled to assume that every occurrence of randStr in your program can be replaced by whatever value it first calculates for it.
Unlike an imperative language, Haskell programs are not a sequence of statements to execute, which perform side effects such as updating state. Haskell programs consist of expressions, which are evaluated more or less in whatever order the compiler deems best. (In particular there is the main expression, which describes what your entire program will do - this is then converted by the compiler and runtime into executable machine code.) So when you assign a complex expression to a variable, you are not saying "at this point in the execution flow, do this calculation and assign the result to this variable". You are saying that "this is the value of the variable", for "all time" - that value isn't allowed to change.
Indeed the only reason that it seems to change here is because you have used unsafePerformIO. As the name itself says, this function is "unsafe" - it should basically never be used, at least unless you really know exactly what you're doing. It is not supposed to be a way of "cheating", as you use it here, to use IO, and thereby generate an "impure" result that may be different in different parts of the program, but pretend the result is pure. It's hardly surprising that this doesn't work.
Since generating random values is inherently impure, you need to do the whole thing in the IO monad, as #DanielWagner has shown one approach for in his answer.
(There is actually another way, involving taking a random generator and functions like randomR to generate a random value together with a new generator. This allows you to do more in pure code, which is generally preferable - but it takes more effort, likely including using the State monad to simplify the threading through of the generator values, and you'll still need IO in the end to make sure you get a new random sequence each time you run the program.)

Write list of random numbers to file. No Instance for (Show (IO a0))

I am trying to write to file a list of random Integers in a file. There seems to be a problem with writeFile here. When I use my function randomFile it says no instance for (Show (IO a0)). I see writeFile doesn't print anything to screen but IO(), so when I call the function randomFile 1 2 3 it says no Instance for Show (IO a0) but actually I just want to execute the function and not have to print anything but how can I avoid this problem. I might be making a lot of errors here. Any help.
import Control.Monad
import Control.Applicative
import System.Random
randNo mind maxd = randomRIO (mind,maxd)
randomFile mind maxd noe = do
let l=(replicate (fromInteger(noe ^ noe)) ( mind `randNo` maxd))
writeFile "RFile.txt" (show l)
I think you have a misunderstanding of what IO is. If you haven't done it, I strongly recommend going through the Input and Output section of Learn You a Haskell.
IO doesn't necessarily have anything to do with print. In Haskell every entry in memory that was made by your own code is considered "pure" while any entry that touches the rest of the computer lives in IO (with some exceptions you will learn about over time).
We model IO using something called a Monad. Which you will learn more about the longer you do Haskell. To understand this, let's look at an example of some code that does and doesn't use IO:
noIOused :: Int -> Int
noIOused x = x + 5
usesIO :: Int -> IO Int
usesIO x = print x >> return (x + 5)
usesIO2 :: Int -> IO Int
usesIO2 x = do
print x
return (x + 5)
The first function is "pure". The second and third functions have an IO "effect" that comes in the form of printing to the screen. usesIO and usesIO2 are just 2 different ways of doing the same thing (it's the same code but with different syntax). I'll use the second format, called do notation from here.
Here are some other ways you could have had IO effects:
add5WithFile :: Int -> IO Int
add5WithFile x = do
writeFile "someFile.txt" (show x)
return (x + 5)
Notice that in that function we didn't print anything, we wrote a file. But writing a file has a side effect and interacts with the rest of the system. So any value we return has to get wrapped in IO.
addRandom :: Int -> IO Int
addRandom x = do
y <- randomRIO (1,10)
return (x + y)
In addRandom we called randomRIO (1,10). But the problem is that randomRIO doesn't return an Int. It returns an IO Int. Why? Because in order to get true randomness we need to interact with the system in some way. To get around that, we have to temporarily strip away the IO. That's where this line comes in:
y <- randomRIO (1,10)
That <- arrow tells us that we want a y value outside of IO. For as long as we remain inside the do syntax that y value is going to be "pure". Now we can use it just like any other value.
So for example we couldn't do this:
let w = x + (randomRIO (1,10))
Because that would be trying to add Int to IO Int. And unfortunately our + function doesn't know how to do that. So first we have to "bind" the result of randomRIO to y before we can add it to x.
Now let's look at your code:
let l=(replicate (fromInteger(noe ^ noe)) ( mind `randNo` maxd))
writeFile "RFile.txt" (show l)
The type of l is actually IO a0. It's a0 because you haven't told the compiler what kind of number you want. So it doesn't know if you want a fraction, a double, a big integer or whatever.
So the first problem is to let the compiler know a little bit more about what kind of random number you want. We do this by adding a type annotation:
randNo :: Int -> Int -> IO Int
randNo mind maxd = randomRIO (mind,maxd)
Now both you and the compiler knows what kind of value randNo is.
Now we need to "bind" that value inside of the do notation to temporarily escape IO. You might think that would be simple, like this:
randomFile mind maxd noe = do
l <- replicate (fromInteger(noe ^ noe)) ( mind `randNo` maxd)
writeFile "RFile.txt" (show l)
Surely that will "bind" the IO Int to l right? Unfortunately not. The problem here is that replicate is a function of the form Int -> a -> [a]. That is, given a number and a type, it will give you a list of that type.
If you give replicate an IO Int it's going to make [IO Int]. That actually looks more like this: List (IO Int) except we use [] as syntactic sugar for lists. Unfortunately if we want to "bind" an IO value to something with <- it has to be the out-most type.
So what you need is a way to turn an [IO Int] into an IO [Int]. There are two ways to do that. If we put \[IO a\] -> IO \[a\] into Hoogle we get this:
sequence :: Monad m => [m a] -> m [a]
As I mentioned before, we generalise IO to something called a Monad. Which isn't really that big a deal, we could pretend that sequence has this signature: sequence :: [IO a] -> IO [a] and it would be the same thing just specialised to IO.
Now your function would be done like this:
randomFile mind maxd noe = do
l <- sequence (replicate (fromInteger(noe ^ noe)) ( mind `randNo` maxd))
writeFile "RFile.txt" (show l)
But a sequence followed by replicate is something people have to do all the time. So someone went and made a function called replicateM:
replicateM :: Monad m => Int -> m a -> m [a]
Now we can write your function like this:
randomFile mind maxd noe = do
l <- replicateM (fromInteger(noe ^ noe)) ( mind `randNo` maxd)
writeFile "RFile.txt" (show l)
And for some real Haskell magic, you can write all 3 lines of code in a single line, like this:
randomFile mind maxd noe = randomRIO >>= writeFile "RFile.txt" . replicateM (fromInteger(noe ^ noe))
If that looks like gibberish to you, then there's a lot you need to learn. Here is the suggested path:
If you haven't already, start from the beginning with Learn You a Haskell
Then learn about how You could have invented Monads
Then learn more about how to use randomness in Haskell
Finally see if you can complete the 20 intermediate Haskell exercises

Random numbers and getStdGen

From the Haskell wikibook we have:
import Control.Monad
import Control.Monad.Trans.State
import System.Random
type GeneratorState = State StdGen
rollDie :: GeneratorState Int
rollDie = do
generator <- get
let (value, newGenerator) = randomR (1,6) generator
put newGenerator
return value
If we execute:
evalState rollDie (mkStdGen 0)
then we get a return type of Int.
This much I understand, but I am wondering if it is possible to wire into this logic the use of the system generator accessed by the function getStdGen. The getStdGen function operates in the IO monad, and my question is (surely this must be the MOST often asked Haskell question) how can you get the generator out of the IO context to use in the non IO monad code above?
Apologies for the newbie question. I am aware that one should not use unsafePerformIO, but otherwise perplexed.
The core of the matter is that it's impossible to produce random numbers out of thin air. You either seed a generator with some input (that's what you do with mkStdGen 0), or you can take the system one with getStdGen.
The problem with mkStdGen 0 is that it's a constant, so your stream of random numbers will show its pseudo-randomness very clearly by being a constant too.
The problem with getStdGen is that it's in the IO monad.
You can't get the generator out of IO to use in a pure computation, that's the whole point of making IO a monad. But within IO, you can bind it using the normal do-notation:
main = do
gen <- getStrGen
print $ evalState rollDie gen
Of course, it'll only produce unique results once.
I heartily concur with the comment recommending MonadRandom. I've been using it for my random-based computations since I found out about it, and haven't looked back.

Reading a file into an array of strings

I'm pretty new to Haskell, and am trying to simply read a file into a list of strings. I'd like one line of the file per element of the list. But I'm running into a type issue that I don't understand. Here's what I've written for my function:
readAllTheLines hdl = (hGetLine hdl):(readAllTheLines hdl)
That compiles fine. I had thought that the file handle needed to be the same one returned from openFile. I attempted to simply show the list from the above function by doing the following:
displayFile path = show (readAllTheLines (openFile path ReadMode))
But when I try to compile it, I get the following error:
filefun.hs:5:43:
Couldn't match expected type 'Handle' with actual type 'IO Handle'
In the return type of a call of 'openFile'
In the first argument of 'readAllTheLines', namely
'(openFile path ReadMode)'
In the first argument of 'show', namely
'(readAllTheLines (openFile path ReadMode))'
So it seems like openFile returns an IO Handle, but hGetLine needs a plain old Handle. Am I misunderstanding the use of these 2 functions? Are they not intended to be used together? Or is there just a piece I'm missing?
Use readFile and lines for a better alternative.
readLines :: FilePath -> IO [String]
readLines = fmap lines . readFile
Coming back to your solution openFile returns IO Handle so you have to run the action to get the Handle. You also have to check if the Handle is at eof before reading something from that. It is much simpler to just use the above solution.
import System.IO
readAllTheLines :: Handle -> IO [String]
readAllTheLines hndl = do
eof <- hIsEOF hndl
notEnded eof
where notEnded False = do
line <- hGetLine hndl
rest <- readAllTheLines hndl
return (line:rest)
notEnded True = return []
displayFile :: FilePath -> IO [String]
displayFile path = do
hndl <- openFile path ReadMode
readAllTheLines hndl
To add on to Satvik's answer, the example below shows how you can utilize a function to populate an instance of Haskell's STArray typeclass in case you need to perform computations on a truly random access data type.
Code Example
Let's say we have the following problem. We have lines in a text file "test.txt", and we need to load it into an array and then display the line found in the center of that file. This kind of computation is exactly the sort situation where one would want to use a random access array over a sequentially structured list. Granted, in this example, there may not be a huge difference between using a list and an array, but, generally speaking, list accesses will cost O(n) in time whereas array accesses will give you constant time performance.
First, let's create our sample text file:
test.txt
This
is
definitely
a
test.
Given the file above, we can use the following Haskell program (located in the same directory as test.txt) to print out the middle line of text, i.e. the word "definitely."
Main.hs
{-# LANGUAGE BlockArguments #-} -- See footnote 1
import Control.Monad.ST (runST, ST)
import Data.Array.MArray (newArray, readArray, writeArray)
import Data.Array.ST (STArray)
import Data.Foldable (for_)
import Data.Ix (Ix) -- See footnote 2
populateArray :: (Integral i, Ix i) => STArray s i e -> [e] -> ST s () -- See footnote 3
populateArray stArray es = for_ (zip [0..] es) (uncurry (writeArray stArray))
middleWord' :: (Integral i, Ix i) => i -> STArray s i String -> ST s String
middleWord' arrayLength = flip readArray (arrayLength `div` 2)
middleWord :: [String] -> String
middleWord ws = runST do
let len = length ws
array <- newArray (0, len - 1) "" :: ST s (STArray s Int String)
populateArray array ws
middleWord' len array
main :: IO ()
main = do
ws <- words <$> readFile "test.txt"
putStrLn $ middleWord ws
Explanation
Starting with the top of Main.hs, the ST s monad and its associated function runST allow us to extract pure values from imperative-style computations with in-place updates in a referentially transparent manner. The module Data.Array.MArray exports the MArray typeclass as an interface for instantiating mutable array data types and provides helper functions for creating, reading, and writing MArrays. These functions can be used in conjunction with STArrays since there is an instance of MArray defined for STArray.
The populateArray function is the crux of our example. It uses for_ to "applicatively" loop over a list of tuples of indices and list elements to fill the given STArray with those list elements, producing a value of type () in the ST s monad.
The middleWord' helper function uses readArray to produce a String (wrapped in the ST s monad) that corresponds to the middle element of a given STArray of Strings.
The middleWord function instantiates a new STArray, uses populateArray to fill the array with values from a provided list of strings, and calls middleWord' to obtain the middle string in the array. runST is applied to this whole ST s monadic computation to extract the pure String result.
We finally use our middleWord function in main to find the middle word in the text file "test.txt".
Further Reading
Haskell's STArray is not the only way to work with arrays in Haskell. There are in fact Arrays, IOArrays, DiffArrays and even "unboxed" versions of all of these array types that avoid using the indirection of pointers to simply store "raw" values. There is a page on the Haskell Wikibook on this topic that may be worth some study. Before that, however, looking at the Wikibook page on mutable objects may give you some insight as to why the ST s monad allows us to safely compute pure values from functions that use imperative/destructive operations.
Footnotes
1 The BlockArguments language extension is what allows us to pass a do block directly to a function without any parentheses or use of the function application operator $.
2 As suggested by the Hackage documentation, Ix is a typeclass mainly meant to be used to specify types for indexing arrays.
3 The use of the Integral and Ix type constraints may be a bit of overkill, but it's used to make our type signatures as general as possible.

Dice Game in Haskell

I'm trying to spew out randomly generated dice for every roll that the user plays. The user has 3 rolls per turn and he gets to play 5 turns (I haven't implemented this part yet and I would appreciate suggestions).
I'm also wondering how I can display the colors randomly. I have the list of tuples in place, but I reckon I need some function that uses random and that list to match those colors. I'm struggling as to how.
module Main where
import System.IO
import System.Random
import Data.List
diceColor = [("Black",1),("Green",2),("Purple",3),("Red",4),("White",5),("Yellow",6)]
{-
randomList :: (RandomGen g) -> Int -> g -> [Integer]
random 0 _ = []
randomList n generator = r : randomList (n-1) newGenerator
where (r, newGenerator) = randomR (1, 6) generator
-}
rand :: Int -> [Int] -> IO ()
rand n rlst = do
num <- randomRIO (1::Int, 6)
if n == 0
then doSomething rlst
else rand (n-1) (num:rlst)
doSomething x = putStrLn (show (sort x))
main :: IO ()
main = do
--hSetBuffering stdin LineBuffering
putStrLn "roll, keep, score?"
cmd <- getLine
doYahtzee cmd
--rand (read cmd) []
doYahtzee :: String -> IO ()
doYahtzee cmd = do
if cmd == "roll"
then rand 5 []
else do print "You won"
There's really a lot of errors sprinkled throughout this code, which suggests to me that you tried to build the whole thing at once. This is a recipe for disaster; you should be building very small things and testing them often in ghci.
Lecture aside, you might find the following facts interesting (in order of the associated errors in your code):
List is deprecated; you should use Data.List instead.
No let is needed for top-level definitions.
Variable names must begin with a lower case letter.
Class prerequisites are separated from a type by =>.
The top-level module block should mainly have definitions; you should associate every where clause (especially the one near randomList) with a definition by either indenting it enough not to be a new line in the module block or keeping it on the same line as the definition you want it to be associated with.
do introduces a block; those things in the block should be indented equally and more than their context.
doYahtzee is declared and used as if it has three arguments, but seems to be defined as if it only has one.
The read function is used to parse a String. Unless you know what it does, using read to parse a String from another String is probably not what you want to do -- especially on user input.
putStrLn only takes one argument, not four, and that argument has to be a String. However, making a guess at what you wanted here, you might like the (!!) and print functions.
dieRoll doesn't seem to be defined anywhere.
It's possible that there are other errors, as well. Stylistically, I recommend that you check out replicateM, randomRs, and forever. You can use hoogle to search for their names and read more about them; in the future, you can also use it to search for functions you wish existed by their type.

Resources