Lecture 2 — 2015-09-07

More Haskell

This lecture is written in literate Haskell; you can download the raw source.

module Lec02 where

We added this statement so that we could use definitions from Lec01.

import Lec01 

We started by writing some functions over the IntTree structure that appears in the homework.

data IntTree = 
  Empty
  | Node IntTree Int IntTree
  deriving Show

leaf :: Int -> IntTree
leaf v = Node Empty v Empty

inorder :: IntTree -> [Int]
inorder Empty = []
inorder (Node l v r) = inorder l ++ [v] ++ inorder r

preorder :: IntTree -> [Int]
preorder Empty = []
preorder (Node l v r) = [v] ++ preorder l ++ preorder r

height :: IntTree -> Int
height Empty = 0
height (Node l _ r) = 1 + max (height l) (height r)

It took a minute to remember our BST invariants, but we wrote a simple lookup function.

memberBST :: Int -> IntTree -> Bool
memberBST _ Empty = False
memberBST v (Node l v' r)
  | v == v' = True
  | v < v' = memberBST v l
  | v > v' = memberBST v r

Next, we tried to think about what it means for a tree to be balanced. We derived some requirements on the board and translated pretty directly to Haskell—nice!

balanced :: IntTree -> Bool             
balanced Empty = True
balanced (Node l _ r) =
  balanced l && balanced r &&
  abs (height l - height r) <= 1

Once we tested on some bigger trees, we decided this was a bit too slow.

bigBalancedTree :: Int -> IntTree  
bigBalancedTree 0 = Empty
bigBalancedTree n = 
  Node (bigBalancedTree (n-1)) 0 (bigBalancedTree (n-1))
  
bigUnbalancedTree :: Int -> IntTree
bigUnbalancedTree n
  | n <= 0 = Node Empty 0 $ Node Empty 1 $ Node Empty 2 Empty
  | even n = Node (bigUnbalancedTree (n-2)) 0 (bigUnbalancedTree (n-1))
  | otherwise = Node (bigUnbalancedTree (n-1)) 0  (bigUnbalancedTree (n-2))

We rewrote our function to carefully computer balanced-ness and height at the same time, using pairs.

balanced' :: IntTree -> (Bool,Int)
balanced' Empty = (True,0)
balanced' (Node l _ r) =
  let (bl,hl) = balanced' l in
  let (br,hr) = balanced' r in
  (bl && br && abs (hl - hr) <= 1, 1 + max hl hr)

After running :set +s to get some performance stats, we played with some other performance-oriented code.

We saw two ways of writing auxiliary functions. At the top level:

fib' :: Int -> Int
fib' n = fibAux n 1 1

fibAux :: Int -> Int -> Int -> Int
fibAux 0 f1 f2 = f1
fibAux 1 f1 f2 = f2
fibAux n f1 f2 = fibAux (n-1) f2 (f1+f2)

And in a where clause:

fib'' :: Int -> Int
fib'' n = fibAux n 1 1
  where fibAux :: Int -> Int -> Int -> Int
        fibAux 0 f1 f2 = f1
        fibAux 1 f1 f2 = f2
        fibAux n f1 f2 = fibAux (n-1) f2 (f1+f2)

I confusingly used the name loop at first. There’s no loop keyword in Haskell! It was just a name! A rose by another name would smell just as sweet.

We also looked at some lazy evaluation. First we had to check out some polymorphic functions, like id (of type a -> a) and map (of type (a->b)->[a]->[b]), and then zipWith :: (a -> b -> c) -> [a] -> [b] -> [c], which led us to this incredible looking definition of the Fibonacci series:

fibs :: [Int]
fibs = 1:1:(zipWith (+) fibs (tail fibs))

The secret sauce in the above definition is lazy evaluation, which we’ll look at more later in the semester. If it didn’t make sense, don’t worry! We’ll see a bit more on Wednesday, and then more formally next week.

Finally, there was some talk about so-called “algebraic” datatypes, where we could define non-recursive datatypes using either the data-style definitions or using Either and tuples/pairs (,).

type Line' = Either Float (Float,Float)

mkLine :: Point -> Point -> Maybe Line
mkLine p1 p2 =
  if xCoord p1 == xCoord p2
  then if yCoord p1 == yCoord p2 
       then Nothing
       else Just $ Vert $ xCoord p1
  else 
    let m = (yCoord p1 - yCoord p2) / 
            (xCoord p1 - xCoord p2) in
    let b = yCoord p1 - (m * xCoord p1) in
    Just $ Sloped m b

either' :: (a -> c) -> (b -> c) -> Either a b -> c
either' l r (Left a) = l a
either' l r (Right b) = r b

As a final puzzle, we thought about how you might write down the algebraic datatype for IntTree—it was recursive! We’ll look at these kinds of recursive definitions in more detail later in the course.