Learn You a Haskell for Great Good! - Miran Lipovaca [84]
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
add _ = putStrLn "The add command takes exactly two arguments"
If add is applied to a list that doesn’t have exactly two elements, the first pattern match will fail, but the second one will succeed, helpfully informing users of their erronous ways. We can add a catchall pattern like this to view and remove as well.
Note that we haven’t covered all of the cases where our input is bad. For instance, suppose we run our program like this:
./todo
In this case, it will crash, because we use the (command:argList) pattern in our do block, but that doesn’t consider the possibility that there are no arguments at all! We also don’t check to see if the file we’re operating on exists before trying to open it. Adding these precautions isn’t hard, but it is a bit tedious, so making this program completely idiot-proof is left as an exercise to the reader.
Randomness
Many times while programming, you need to get some random data (well, pseudo-random data, since we all know that the only true source of randomness is a monkey on a unicycle with cheese in one hand and its butt in the other). For example, you may be making a game where a die needs to be thrown, or you need to generate some data to test your program. In this section, we’ll take a look at how to make Haskell generate seemingly random data and why we need external input to generate values that are random enough.
Most programming languages have functions that give you back some random number. Each time you call that function, you retrieve a different random number. How about Haskell? Well, remember that Haskell is a purely functional language. That means it has referential transparency. And that means a function, if given the same parameters twice, must produce the same result twice. That’s really cool, because it allows us to reason about programs, and it enables us to defer evaluation until we really need it. However, this makes it a bit tricky for getting random numbers.
Suppose we have a function like this:
randomNumber :: Int
randomNumber = 4
It’s not very useful as a random number function, because it will always return 4. (Even though I can assure you that the 4 is completely random, because I used a die to determine it.)
How do other languages make seemingly random numbers? Well, they take some initial data, like the current time, and based on that, generate numbers that are seemingly random. In Haskell, we can generate random numbers by making a function that takes as its parameter some initial data, or randomness, and produces a random number. We use I/O to bring randomness into our program from outside.
Enter the System.Random module. It has all the functions that satisfy our need for randomness. Let’s just dive into one of the functions it exports: random. Here is its type signature:
random :: (RandomGen g, Random a) => g -> (a, g)
Whoa! We have some new type classes in this type declaration! The RandomGen type class is for types that can act as sources of randomness. The Random type class is for types whose values can be random. We can generate random Boolean values by randomly producing either True or False. We can also generate numbers that are random. Can a function take on a random value? I don’t think so! If we try to translate the type declaration of random to English, we get something like this: It takes a random generator (that’s our source of randomness) and returns a random value and a new random generator. Why does it also return a new generator as well as a random value? Well, you’ll see in a moment.
To use our random function, we need to get our hands on one of those random generators. The System.Random module exports a cool type, namely StdGen, which is an instance of the RandomGen type class. We can make a StdGen manually, or we can tell the system to give us one based on a multitude of (sort of) random stuff.
To manually make a random generator, use the mkStdGen function. It has a type of mkStdGen :: Int -> StdGen. It takes an