Lab 2: Haskell

Overview

In class this week, we are starting to become familiar with the design and features of Haskell, a purely functional, lazy programming language.

In today’s lab, you and your partner will become familiar with writing, compiling, and running Haskell code, with a particular focus on error messages.

In this week’s assignment, you will use Haskell to do more substantial things.

Getting Haskell (0 minutes)

The Glasgow Haskell Compiler is installed on CS department machines. There is a command-line version ghc (which produces executable machine code as clang++ and javac do) and an interactive version ghci (which lets you type in expressions, compiles them, runs them, and prints the answers).

We assume you will ssh to knuth and work there (as last week).

Getting the lab files (5 minutes)

As we did last week, use the assignment workflow and the link provided on Piazza to get access to this week’s lab files.

Warmup (15 minutes)

  1. Start ghci and get the prompt Prelude>. When you need to, you can exit ghci by entering the end-of-file keystroke Control-D at the prompt.

  2. Try evaluating various Haskell expressions, such as:

     map (\x -> x / 2) [2.25, 3.5, 4.5, 7.2, 6.5]
     map (\x -> 2 / x) [2.25, 3.5, 4.5, 7.2, 6.5]
     map (/ 2) [2.25, 3.5, 4.5, 7.2, 6.5]
     map (2 /) [2.25, 3.5, 4.5, 7.2, 6.5]
     filter (\x -> x > 5) [2.25, 3.5, 4.5, 7.2, 6.5]
     filter (> 5) [2.25, 3.5, 4.5, 7.2, 6.5]
     filter (> "Fun") ["C", "Fortran", "Haskell", "ML"]
     reverse [1,2,3,4,5]
     reverse "Haskell"
     show 17
     show [1,2,3,4]
     reverse (show [1,2,3,4])
     show (reverse [1,2,3,4])
     [22..47]
     take 15 [22..47]
     drop 15 [22..47]
     1 : [2,3,4]
     ['h','e','l','l','o']
     'h' : "ello"
     "Hello" ++ " " ++ "World"
     [1,2,3,4] ++ [40,50,60,70]
     [2,3,4] ++ [1]
     import Data.List
     partition (> 5) [2.25, 3.5, 4.5, 7.2, 6.5]
     partition (> "Fun") ["C", "Fortran", "Haskell", "ML"]
     sort [80, 38, 23, 40, 70, 24, 8, 92, 78, 80, 42, 19, 95, 87, 4]
     sort "haskell = HASKELL"
    
  3. Make sure you understand the difference between these lines:

     reverse "ABC"
     :type reverse "ABC"
     reverse
     :type reverse
    
  4. Come up with at least five more sample expressions to try.

Practice Reading Error Messages (20 minutes)

The files ex1.hs through ex4.hs contain errors. You can load a file such as ex1.hs into ghci by starting ghci in the directory that contains the file and using the command :load ex1. Load each file into ghci, and fill out the corresponding section of the file diagnose.txt.

Tips for diagnosing and fixing Haskell error messages

  • All error messages are annotated with the filename, row, and column where ghc first detected an error. This is a good place to start your search for the error. (But also read the error message to try to understand exactly how ghc became confused.)

  • If you get lots of error messages, it is always a good idea to scroll back and see if you can fix the first error. It is often a good idea to recompile as soon as the first error is fixed. Quite frequently, one bug either leads to multiple error messages (so fixing the first error makes many error messages disappear), or the first bug confuses ghc enough that it starts complaining about perfectly good code (so that the later “error messages” are bogus).

  • There is one error that you are guaranteed to make this semester, so it’s worth learning to recognize the error message.

    If you see an error message that looks like this:

        error: Parse error in pattern: «function name»
    

    here’s what’s going on: Haskell has special syntax for defining curried functions (f a b c = ...) where you can give names to the many arguments. It turns out that if you define a function with a complicated unparenthesized pattern (g h:t = ...) then the Haskell parser interprets this as an attempt to define a curried function (g h : t = ...), then gets totally confused because : by itself is not a valid pattern for the second argument. So the Haskell rule is: if you define a function with a complex pattern, always put grouping parentheses around the pattern (g (h:t) = ...).

  • This class is not CS 131: Interpreting GHC Type Errors. Spending 20 minutes just staring at an error message is not particularly educational. If it’s taking you a long time to figure out an error message, use Stack Overflow, or ask a friend, a grutor, a prof, or Piazza for help.

Defining Functions (20 minutes)

Put the following function definitions in lab2.hs. Recall that you can load this file into ghci with the :load or :l command. (And you will need to reload the file into ghci every time you make changes, or you’ll be testing a stale version of the code. The :reload or :r command might also be helpful.)

  1. Define the function tnpo :: Integer -> Integer that works like the Tnpo macro in Homework 1.

    Test the tnpo function in ghci with several inputs.

  2. Define the recursive function rep :: Integer -> (a -> a) -> a -> a that turns Haskell integers into Church numerals.

    From the type, we see that rep takes three arguments (via currying). We expect these equations to hold, along with all similar equations:

        rep 0 f b  ==  b
        rep 3 f b  ==  f (f (f b))
        rep 5 f b  ==  f (f (f (f (f b))))
    

    You can test your definition in ghci by, for example, verifying that (rep 5 succ) 2 returns 7 and that (rep 4 (* 2)) 1 returns 16.

  3. Define the recursive function filt :: (a -> Bool) -> [a] -> [a] that works like the built-in filter function i.e., it returns the elements of the given list that make the given predicate return True.

    Verify that filt even [1,2,3,4,5] returns the list [2,4].

Don’t forget to commit your work to the subversion repository at the end of the lab period.

If you finish early…

If you finish early, you can take a look at the page that describes how to test Haskell code.

Testing is a strongly encouraged (but not required) part of the next assignment.