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.