Haskell Exercises

Note: The notation "[Slide N]" means that the exercise should be "in range" after we've covered slide N.

  1. [Slide 43] Rewrite the following expressions using Haskell function call syntax. Use as few parentheses as possible.

    f(x) * a == g(a) + c
        
    h(f(a) + g(x-y))
        
    f(g(a*-b)-h(b))
    
    f(x) * a == g(a) + c
        f x * a == g a + c
    	
    h(f(a) + g(x-y))
        h (f a + g (x - y))
    	
    f(g(a*-b)-h(b))
        f (g (a * (-b)) - h b)
        More idiomatic: f (g (a * negate b) - h b )
    
  2. [Slide 47] In ghci use :m +Data.Char to load the Data.Char module. Then do :browse Data.Char to see all the functions in the module. (You'll see a long data declaration for GeneralCategory , too. Ignore it for now.) Try ord, chr, isPunctuation, isHexDigit, and toTitle. Regarding values for ord and chr, Google for ASCII chart. (See the problem below re pred 'A', too.)

  3. [Slide 47] What does pred 'A' produce, and why?

    Also, see if you can find an argument for pred that will produce this exception: Prelude.Enum.Char.pred: bad argument

    Re pred 'A', before I started memorizing the ASCII codes I usually did man ascii to answer questions like that. There are also lots of ASCII charts on the web. One is http://www.asciitable.com

    Re making pred throw an exception, there's nothing before the first character, which has a numeric value of zero. In Haskell character literals we can use \N to specify the Nth Unicode character. (The first 128 characters of Unicode are the same as ASCII.) Haskell also recognizes ASCII mnemonics like NUL and BEL, too.

    > pred '\0'
    *** Exception: Prelude.Enum.Char.pred: bad argument
    
    > pred '\NUL'
    *** Exception: Prelude.Enum.Char.pred: bad argument
    

    p.s.
    I started memorizing the ASCII codes about 35 years after I should have. I hope you'll start sooner than I did! Here's one way to learn them: http://www.memrise.com/course/80243/ascii-to-decimal

  4. Hoogle searches Haskell documentation. For example, hit http://www.haskell.org/hoogle/?hoogle=toUpper to search for functions named toUpper. (Suggestion: Set up a Chrome "Search Engine" for Hoogle—see O(1) Navigation on the Web with "Custom Search Engines".)

  5. [Slide 59] Try :type on the following functions: even, id, pred, abs, signum, log, and logBase. Then try some calls to the functions.

  6. [Slide 59] Try :info on these types/type classes: Eq, Bool, Int, Ord, Bounded. Several end in many lines of this general form: instance (Eq a, Eq b, Eq c) => Eq (a, b, c). Ignore that stuff for now. Focus on what precedes it.

  7. [Slide 59] Try :type pi and :type minBound. Are they functions?

  8. [Slide 59] Try these expressions, which use :: to request that the value have a specific type.

    minBound::Char
    maxBound::Bool
    maxBound::Int
    maxBound::Integer (it's an error, but why?)
    
  9. The diagram on slide 56 shows a big "X" on the arrow from Eq to Num. That's because I claim that implied relationship doesn't actually exist.

    Use :info on Ord, Num, and Real, and see if you believe my "correction" is correct.

  10. [Slide 66] Create a file named simple.hs with the contents shown on slide 66 and work through the examples on slide 67-68 with running ghci, using :load, :reload, etc.
  11. [Slide 68] Try the function definitions on slide 64 and then write some single-argument functions:

    Define the above using only operators, which are on slide 84. Don't use if-elses or guards, which we'll cover later.

    I typically work out small pieces of code at the ghci prompt, and then move that code into a file.

    Use :set +t to cause the type of each to be automatically shown.

    isZero1 x = x == 0
    
    isZero2 x = x == 0.0
    
    isNonZero x = not (isZero1 x) -- note use of not
    
    ltRecip x = x < recip x
    
    ltSelf x = x < x
    
    isIOU c = c == 'I' || c == 'O' || c == 'U'
    
    notIsIOU c = not (isIOU c) -- note use of not
    
    area r = pi * r ^ 2
    
  12. Get creative: Think up three more significantly different single-argument functions. Mail me if you come up with some good ones.

  13. [Slide 77] The material on partial applications is critical! Here's a whole batch of exercises on partial applications.

  14. On slide 80 the names add and plus are both bound to the same function value.

    Speculate: can add and plus be compared?

    Try it!

  15. [Slide 82] Given let plus=(+), what's plus?

  16. [Slide 89] Define functions f1 through f6 whose types are inferred to be the following. The functions don't need to perform any meaningful computation—only the type matters. You may NOT use ::type (as shown on slide 65) to force types.

    f1 :: Eq a => a -> Bool
    
    f2 :: Num a => t -> a
    
    f3 :: Fractional a => t -> a
    
    f4 :: Int -> Int
    
    f5 :: (Bounded a, Ord a) => a -> Bool
    
    f6 :: (Bounded a, Eq a) => a -> Bool
    
    let f1 x = x == x
    
    let f2 x = 10
    
    let f3 x = 1.0
    
    let f4 x = ord 'a' + x -- assumes ':m +Data.Char' done previously or in ~/.ghci
    
    let f5 x = x > minBound
    
    let f6 x = x == minBound
    
  17. Get creative: Imagine a function type and then write a function that will be inferred to have that type. Do two more.

  18. [Slide 95] Make a copy of simple.hs from above and experiment with indentation and continuation as shown on slides 94-95.

  19. [Slide 99] Write a function tempTo units temperature to convert a temperature to either Celsius or Fahrenheit. It works like this:

    > tempTo 'c' 32
    0.0
    
    > tempTo 'f' 0
    32.0
    
    tempTo u t
        | u == 'f' = (t * 9/5) + 32
        | u == 'c' = (t - 32) * 5/9
    
  20. Given tempTo in the previous problem, create toC and toF:

    > toC 212
    100.0
    
    > toF 0
    32.0
    
    toC = tempTo 'c'
    toF = tempTo 'f'
    
  21. Slide 4 says, "A paradigm has a world view, a vocabulary, and a set of techniques that can be applied to solve a problem." What are some terms in the vocabulary of functional programming?

  22. [Slide 109] Write a function sumDigits that returns the sum of the digits of an integer. Be sure to handle negative values, too.

    > sumDigits (2^200)
    256
    > sumDigits (-123)
    6
    
    sumDigits :: Integral a => a -> a
    sumDigits n
        | n < 0  = sumDigits (negate n)
        | n == 0 = 0
        | otherwise = n `rem` 10 + sumDigits (n `div` 10)
    
  23. [Slide 109] Pretend you've never heard of Gauss's summation formulas and write recursive functions sumN and sumMtoN to compute the sum of the first N natural numbers and the natural numbers from M to N, respectively.

    sumN n
        | n == 1 = 1
        | otherwise = n + sumN (n - 1)
        
    sumMtoN from to
        | from > to = 0
        | otherwise = from + sumMtoN (from + 1) to
    
  24. [Slide 115] Predict the type of head, tail, take, drop, reverse, init, and last. Use :t to check yourself.

  25. [Slide 115] In English, what does let t5=take 5 create?

    t5 is a partial application of take that will produce the first five elements of whatever list it's called with.
  26. [Slide 116] Experiment and see whether the arithmetic sequence notation works with arbitrary expressions or only literals.

  27. [Slide 116] Do let v = [1..7] and using various combinations of head, tail, take, drop, reverse, init, and last write two different expressions that produce each of these values:

    1
    6
    7
    [3,4,5]
    [6,5]
    
  28. [Slide 124] Like the previous previous problem but produce "auto", "to", "mat", and "tam" from "automata". (This makes me wonder which English word contains the most words, including reversals.)

  29. [Slide 116] Look at the values in these two lists: [1.0,1.1..2] and [1.0,1.2..2]. Do you see an inconsistency? What is it and why does it exist?

    Let's try them:

    > [1.0,1.1..2]
    [1.0,1.1,1.2000000000000002,1.3000000000000003,1.4000000000000004,1.5000000000000004,1.6000000000000005,1.7000000000000006,1.8000000000000007,1.9000000000000008,2.000000000000001]
    
    > [1.0,1.2..2]
    [1.0,1.2,1.4,1.5999999999999999,1.7999999999999998,1.9999999999999998]
    
    The last value in the first list is less than 2.0 but the last value in the second list is greater than 2.0. This is due to issues with the representation of floating-point numbers.
  30. [10..1] produces an empty list. What's an easy way to create a list running from 10 down to 1?

    reverse [1..10]
    
  31. [Slide 129] What is the value and type of each of the following expressions?

    1:2.0:[]
    []:[]
    [1]:[]
    [1]:[]:[]
    []:[1]:[]
    [length]
    [tail,init]
    [head,tail]
    
  32. [Slide 129] Work through the following operations, drawing the list structure and labeling values.

    let a=5
    let b=[2,3]
    let c=a:b
    let d = a:tail b
    let e = head b:b
    let f = [head b:head c:head (tail d):[]]