CS 224 Lab 1  - A Haskell interlude with Currying and Higher-Order Functions

Objectives

  • Use partial application due to currying
  • Write higher-order functions
  • Use list comprehensions
  • Perform the following tasks in the order given.

    1. Copy the lab1 directory from ~jillz/cs224 on phoenix
       
    2. We will now consider why the type for a function like plus listed below is Int -> Int -> Int rather than something like (Int,Int) ->Int.

      plus :: Int -> Int -> Int
      plus x y = x + y

      Well the expression plus 3 4 is really equivalent to ((plus 3) 4).  The result of plus 3  is then applied to the argument 4.  This means that the value of (plus 3) must also be a function! 

      Indeed, we could define a new function to be the result of (plus 3) as follows:

      plusThree :: Int -> Int
      plusThree = plus 3

      We would get the result that we expect when using this new function.

      > plusThree 4
      7

      This method of applying functions to one argument at a time is called currying (after Haskell B. Curry).  Curried functions can be applied to one argument only, giving another function.  Sometimes these new functions can be useful in their own right.  Consider the following function:

      twice :: (Int -> Int) -> Int -> Int
      twice f x = f (f x)

      The function twice takes as arguments a function and an integer and applies the function twice to the integer argument.  We could use the function resulting in using only the first argument to get the following new functions:

      add2 = twice (+1)

      quad = twice square

      What would be the result of the expressions add2 3  and quad 2 ?  Try them out.
       

    3. The function twice is an example of a higher-order function.  Higher-order functions take functions as parameters and can also return functions.  You should already be familiar with the higher-order functions map and filter.  The function map  takes a function and a list and applies the function to every item in the list.  The function filter takes a predicate and a list and returns a list of all the items that satisfy the predicate.

      We can use filter to define the function quickSort to sort a list.  The idea behind quickSort  is to pick an element x in the list and divide the list into three parts,  all the items less than x, all the items equal to x, and all the items greater than x.  We recursively sort the first and third parts and concatenate them all together.

        
      quickSort :: Ord a => [a] -> [a]
        quickSort [] = []
        quickSort (x : xs) = (quickSort less) ++ (x : equal) ++ (quickSort more)
      where
             
      less = filter (< x) xs
              equal =
      filter (== x) xs
              more =
      filter
      (> x) xs

      We can define
         
      dictionary = ["I", "have", "a", "thing", "for", "Haskell"]

      What happens when we sort this list with quickSort?  The reason is that the typicall ASCII ordering of characters specifies that upper case characters are "less than" lower case characters. 

      To make things more flexible we will redefine quickSort to take an extra argument which will be a comparison function that compares two values returns values LT, EQ, and GT (representing "less than", "equal" and "greater than").

          quickSort' :: (a -> a -> Ordering) -> [a] -> [a]
          quickSort' _ [] = []
          quickSort' c (x : xs) = (quickSort' c less) ++ (x : equal) ++ (quickSort' c more)
      where
             
      less = filter (\y-> y `c` x == LT) xs
              equal =
      filter (\y -> y `c` x == EQ) xs
              more =
      filter (\y ->
      y `c` x == GT) xs

      There is a standard Haskell function compare which is the usual comparison function. Try out
          quickSort' compare dictionary
       

      Now we can write other comparison functions.  For example,

          descending x y = compare y x

      We can use partial application due to currying to define a new function:

          sortDescending :: Ord a => [a] -> [a]
        sortDescending = quickSort' descending

      Try it out.

      Assignment:
      Write a comparison function insensitive which compares Strings but is case-insensitive.  So

       > quickSort' insensitive dictionary

      ["a","for","Haskell","have","I","thing"]

      You will want to use map and the function toLower.


       

    4. Another very useful higher-order function which is already in the standard libray is zipWith that takes a function and two lists as parameters and then joins the two lists by applying the function.

      For example,

          
      > zipWith (+) [4,2,5,6] [2,6,2,3]
          [6,8,7,9]

          > zipWith max [6,3,2,1] [7,3,1,5]
          [7,3,2,5]

       
      Assignment:
      I have hidden zipWith inside the Example1 module.  Define this function yourself.

       zipWith :: (a -> b-> c) -> [a] -> [b] -> [c]


       

    5. Haskell contains a shorthand for filtering a list called a list comprehension.  This shorthand looks very much like set notation.  For example, consider the following which computes the list of even numbers from 0 to 100 by using a filter.

         
      s1 = filter (\x-> x `mod` 2 == 0) [0..100]

      We can perform the exact same computation this way:

          s2 = [x | x<-[0..100], x `mod` 2 == 0]

      Assignment:
      Write a function factors n which computes a list of all the factors of n. 
      Hint:  The factors of n are all between 1 and n `div` 2 and of course evenly divide n.

      factors :: Integer -> [Integer]


       

    6. Send me your modified code in GoucherLearn.