Introduction to Functional Programming -- Exercises Week 4: "IO and Testing"TDA555 / DIT440, LP1, HT2012
Home | Schedule | Labs | Exercises | Exam | AboutFire | Forum | TimeEdit | Links | 2011

Exercises for Week 4: IO, Test Data and Properties

Here are some exercises designed to help you practice programming with IO, test data generation, and properties.

Please prepare yourself for these exercises by printing them out, and also printing out the code samples and documentation that is referred to by links.

If you do not have time to do all these exercises, don't worry. The exercises are intended to provide enough work to keep the most experienced students busy. If you do all exercises marked with an (*) you have probably understood this week's material.

Good luck!

0 (*). Exercises from the Book

Do exercises 18.3, 18.5, and 18.7 from the book.

1 (*). Properties of the Look Function

Consider the following standard Haskell function, that looks up an element in a list of pairs (table):
look :: Eq a => a -> [(a,b)] -> Maybe b
look x []           = Nothing
look x ((x',y):xys)
  | x == x'         = Just y
  | otherwise       = look x xys
Define a property prop_LookNothing that expresses that if the look function delivers Nothing, then the thing we were looking for was not in the table.

Also define a property prop_LookJust that expresses that if the look function delivers a result (Just y), then the pair (x,y) should have been in the table.

Also write one property prop_Look that combines prop_LookNothing and prop_Just into one property.

2. Monadic helper functions

Give an implementation of the following two functions:

sequence_ :: Monad m => [m ()] -> m ()
onlyIf    :: Monad m => Bool -> m () -> m ()
"sequence_" takes a list of instructions resulting in an uninteresting value, and creates one big instruction that executes all of these.

"onlyIf" takes a boolean and an instruction, and creates an instruction that only executes the argument instruction if the boolean was True. If the boolean was False, nothing happens. Example "onlyIf failed tryAgain" executes the instructions tryAgain only if the boolean failed is True.

Hint: You might find it easier to think of the above functions having type:


sequence_ :: [IO ()] -> IO ()
onlyIf    :: Bool -> IO () -> IO ()

What becomes different if we change the type of onlyIf to:


onlyIfM :: Monad m => m Bool -> m () -> m ()
What other kinds of programs can we write now?

Give an implementation of onlyIfM.

3 (*). The Number Guessing Game

In this exercise, you are going to implement the "number guessing game" in Haskell.

Here is an example of how this might work:

  Main> game
  Think of a number between 1 and 100!
  Is it 50? higher
  Is it 75? lower
  Is it 62? lower
  Is it 56? yes
  Great, I won!
The text in italics is what the user types in. The other text is produced by your program.

Implement a function

  game :: IO ()
That plays one instance of this game.

You might need the following functions:

  getLine :: IO String         -- reads a line of user input
  putStrLn :: String -> IO ()  -- outputs one line of text
Before you start programming, think of a good guessing strategy for the computer that minimizes the number of guesses!

4. A Backup Script

Haskell programs can generate instructions to be performed by the command shell using

    system :: String -> IO ()

This is one way to run other programs from Haskell, for example. The String passed to system often differs, depending on whether it is the Linux shell or the Windows one which should obey the command. For example, to copy file A to file B under Linux, the string "cp A B" is used, while under Windows it would be "copy A B". As a result, programs which use system normally work only under one operating system, which is sad, but can't be helped.

Use system to write a program that

  • creates a new directory called "backup", using the "mkdir" command,
  • copies all the files in the current directory into the backup, using cp or copy as appropriate

(You are supposed to copy the files one by one, not by constructing a command such as "cp *  backup", because filename expansion isn't applied to strings passed to system.)

You will need to read the list of filenames in the current directory, and to find out how, you will need to consult the documentation of Haskell's standard libraries. Here's the link:

http://www.haskell.org/ghc/docs/latest/html/libraries/index.html

There are a lot of them! The one you want is called System.Directory--find its documentation, and figure out how to read the filenames in a directory.

You will also perhaps need to perform all of a list of actions. You may find the function

    sequence :: Monad m => [m a] -> m [a]

useful for this. You must add

    import Monad

to your file if you want to use it.

If you want to, you can also use the standard functions createDirectory and copyFile from System.Directory (instead of the commands "mkdir" and "cp") to accomplish this.

5 (*). Generating Lists

Sometimes we want to generate lists of a certain length.

A. Write a generator

    listOfLength :: Integer -> Gen a -> Gen [a]

such that listOf n g generates a list of n elements, where each element is generated by g. What property would you write to test that your generator behaves as it should?

B. Now use listOf to write a generator that generates pairs of lists of the same, random, length.

C.Take a look at the standard Haskell functions zip and unzip:

  zip :: [a] -> [b] -> [(a,b)]
  unzip :: [(a,b)] -> ([a],[b])
Write down two properties for these; one that says that zip is the inverse of unzip, and one that unzip is the inverse of zip. Note that unzip is not always the inverse of zip, so you need a condition! Could you make use of the generator you just defined?

Hint: make a datatype

  data TwoSameLengthLists a = SameLength [a] [a]
and use your generator for part B. to make a generator for the above type. Then, make the above type an instance of Arbitrary. Finally, you can use it to write your property.

Another possibility is to use the QuickCheck function forAll, which we might see later on in the course.

6. Generating Ordered Lists

Write a generator that generates random lists of integers that are sorted (ordered). You are not allowed to make use of the function sort! (And not of the generator orderedList from the lecture either...)

Hint: First generate a list of random, positive integers. Then, generate a first, random number in the list. Then, produce a list where the differences between each consecutive pair of elements is given by the list of positive integers.

Make sure to check that your generator generates ordered lists by writing a suitable property!