Functions and Programming
What is Haskell?
A typed, lazy, purely functional programming language
Haskell = λ-calculus ++
- better syntax
- types
- built-in features
- booleans, numbers, characters
- records (tuples)
- lists
- recursion
- …
Why Haskell?
Haskell programs tend to be simple and correct
QuickSort in Haskell
sort :: (Ord a) => [a] -> [a]
sort [] = []
sort (x:xs) = sort ls ++ [x] ++ sort rs
where
ls = [ l | l <- xs, l <= x ]
rs = [ r | r <- xs, x < r ]
Goals for this week
- Understand the code above
- Understand what typed, lazy, and purely functional means (and why it’s cool)
Haskell vs λ-calculus: similarities
(1) Programs
A program is an expression (not a sequence of statements)
It evaluates to a value (it does not perform actions)
λ:
Haskell:
(2) Functions
Functions are first-class values:
- can be passed as arguments to other functions
- can be returned as results from other functions
- can be partially applied (arguments passed one at a time)
But: unlike λ-calculus, not everything is a function!
(3) Top-level bindings
Like in Elsa, we can name terms to use them later
Elsa:
let T = \x y -> x
let F = \x y -> y
let PAIR = \x y -> \b -> ITE b x y
let FST = \p -> p T
let SND = \p -> p F
eval fst:
FST (PAIR apple orange)
=~> apple
Haskell:
haskellIsAwesome = True
pair = \x y -> \b -> if b then x else y
fst = \p -> p haskellIsAwesome
snd = \p -> p False
-- In GHCi:
> fst (pair "apple" "orange") -- "apple"
The names are called top-level variables
Their definitions are called top-level bindings
Better Syntax: Equations and Patterns
You can define function bindings using equations:
pair x y b = if b then x else y -- same as: pair = \x y b -> ...
fst p = p True -- same as: fst = \p -> ...
snd p = p False -- same as: snd = \p -> ...
A single function binding can have multiple equations with different patterns of parameters:
pair x y True = x -- If 3rd arg matches True,
-- use this equation;
pair x y False = y -- Otherwise, if 3rd arg matches False,
-- use this equation.
At run time, the first equation whose pattern matches the actual arguments is chosen
For now, a pattern is:
a variable (matches any value)
or a value (matches only that value)
Same as:
pair x y True = x -- If 3rd arg matches True,
-- use this equation;
pair x y b = y -- Otherwise, use this equation.
Same as:
QUIZ
Which of the following definitions of pair
is incorrect?
A. pair x y = \b -> if b then x else y
B. pair x = \y b -> if b then x else y
C.
D.
E. all of the above
Equations with guards
An equation can have multiple guards (Boolean expressions):
Same as:
Recusion
Recursion is built-in, so you can write:
or you can write:
The scope of variables
Top-level variable have global scope, so you can write:
message = if haskellIsAwesome -- this var defined below
then "I love CSE 130"
else "I'm dropping CSE 130"
haskellIsAwesome = True
Or you can write:
-- What does f compute?
f 0 = True
f n = g (n - 1) -- mutual recursion!
g 0 = False
g n = f (n - 1) -- mutual recursion!
Is this allowed?
Local variables
You can introduce a new (local) scope using a let
-expression:
Syntactic sugar for nested let
-expressions:
If you need a variable whose scope is an equation, use the where
clause instead:
Types
What would Elsa say?
What would Python say?
What would Java say?
Types
In Haskell every expression either
- ill-typed and rejected at compile time or
- has a type and can be evaluated to obtain _ a value of the same type.
Ill-typed* expressions are rejected statically at compile-time, before execution starts
- like in Java
- unlike λ-calculus or Python …
Why are types good?
- Helps with program design
- Types are contracts (ignore ill-typed inputs!)
- Catches errors early
- Allows compiler to generate code
- Enables compiler optimizations
Type annotations
You can annotate your bindings with their types using ::
, like so:
-- | This is a Boolean:
haskellIsAwesome :: Bool
haskellIsAwesome = True
-- | This is a string
message :: String
message = if haskellIsAwesome
then "I love CSE 130"
else "I'm dropping CSE 130"
-- | This is a word-size integer
rating :: Int
rating = if haskellIsAwesome then 10 else 0
-- | This is an arbitrary precision integer
bigNumber :: Integer
bigNumber = factorial 100
If you omit annotations, GHC will infer them for you
- Inspect types in GHCi using
:t
- You should annotate all top-level bindings anyway! (Why?)
Function Types
Functions have arrow types:
\x -> e
has typeA -> B
- if
e
has typeB
assumingx
has typeA
For example:
Always annotate your function bindings
First understand what the function does
- Before you think about how to do it
When you have *multiple arguments
For example
why? because the above is the same as:
however, as with the lambdas, the ->
associates to the right so we will just write:
Lists
A list is
either an empty list
[] -- pronounced "nil"
or a head element attached to a tail list
h : t -- pronounced "h cons t"
Examples:
[] -- A list with zero elements
1 : [] -- A list with one element: 1
(:) 1 [] -- As above: for any infix op, `x op y` is same as `(op) x y`
1:(2:(3:(4:[]))) -- A list with four elements: 1, 2, 3, 4
1:2:3:4:[] -- Same thing (: is right associative)
[1,2,3,4] -- Same thing (syntactic sugar)
Terminology: constructors and values
[]
and (:)
are called the list constructors
We’ve seen constructors before:
True
andFalse
areBool
constructors0
,1
,2
are … well, you can think of them asInt
constructors- The
Int
constructors don’t take any parameters, we just called them values
- The
In general, a value is a constructor applied to other values
- examples above are list values
The Type of a List
A list has type [Thing]
if each of its elements has type Thing
Examples:
intList :: [Int]
intList = [1,2,3,4]
boolList :: [Bool]
boolList = [True, False, True]
strList :: [String]
strList = ["nom", "nom", "burp"]
Lets write some Functions
Step 1: Write some tests
Step 2: Write the type
Step 3: Write the code
Functions on lists: range
1. Tests
2. Type
3. Code
Syntactic Sugar for Ranges
There’s also syntactic sugar for this!
Functions on lists: length
1. Tests
2. Type
3. Code
Pattern matching on lists
A pattern is either a variable (incl. _
) or a value
A pattern is
- either a variable (incl.
_
) - or a constructor applied to other patterns
Pattern matching attempts to match values against patterns and, if desired, bind variables to successful matches.
Functions on lists: take
Let’s write a function to take
first n
elements of a list xs
.
1. Tests
2. Type
3. Code
QUIZ
Which of the following is not a pattern?
A. (1:xs)
B. (_:_:_)
C. [x]
D. [1+2,x,y]
E. all of the above
Strings are Lists-of-Chars
For example
λ> let x = ['h', 'e', 'l', 'l', 'o']
λ> x
"hello"
λ> let y = "hello"
λ> x == y
True
λ> :t x
x :: [Char]
λ> :t y
y :: [Char]
shout Shout SHOUT
How can we convert a string to upper-case, e.g.
Some useful library functions
-- | Length of the list
length :: [t] -> Int
-- | Append two lists
(++) :: [t] -> [t] -> [t]
-- | Are two lists equal?
(==) :: [t] -> [t] -> Bool
You can search for library functions on Hoogle!
Tuples
(,)
is the pair constructor
Field access
Using fst
and snd
Tuples to pass parameters
but watch out, add2
expects a tuple.
Tuples and Pattern Matching
It is often clearer to use patterns for tuples, e.g.
or equivalently,
or, best, use the pattern in the parameter,
You can use pattern matching not only in equations, but also in λ-bindings and let
-bindings!
QUIZ: Pattern matching with pairs
Is this pattern matching correct? What does this function do?
quiz :: String -> [(String, Int)] -> Int
quiz _ [] = 0
quiz x ((k,v) : ps)
| x == k = v
| otherwise = quiz x ps
What is quiz "dog" [ ("cat", 10), ("dog", 20), ("cat", 30)]
?
A. Type error!
B. 0
C. 10
D. 20
D. 30
Generalized Tuples
Can we implement triples like in λ-calculus?
Sure! but Haskell has native support for n-tuples:
myPair :: (String, Int)
myPair = ("apple", 3)
myTriple :: (Bool, Int, [Int])
myTriple = (True, 1, [1,2,3])
my4tuple :: (Float, Float, Float, Float)
my4tuple = (pi, sin pi, cos pi, sqrt 2)
The “Empty” Tuple
It also makes sense to have an 0-ary tuple:
often used like void
in other languages.
List comprehensions
A convenient way to construct lists!
QUIZ
What is the result of evaluating:
A. Infinite loop B. []
C. [0, 10, 20, 30, 40, 50]
D. 150
E. Type error
Comprehensions and Ranges
Recall you can enumerate ranges as
So, we can write the above more simply
QUIZ: Composing Comprehensions
What is the result of evaluating
A. Type error B. []
C. [0,1]
D. [(0,0), (1,1)]
E. [(0,0), (0,1, (1,0), (1,1)]
QUIZ: Composing Comprehensions
What is the result of evaluating
A. Type error B. []
C. [0,1]
D. [(0,0), (1,1)]
E. [(0,0), (0,1, (1,0), (1,1)]
shout revisited
How can we convert a string to upper-case, e.g.
Use comprehensions to write a *non-recursive" shout
?
QuickSort in Haskell
Step 1: Write some tests
Step 2: Write the type
Step 3: Write the code
sort :: [Int] -> [Int]
sort [] = []
sort (x:xs) = sort ls ++ [x] ++ sort rs
where
ls = [ l | l <- xs, l <= x ]
rs = [ r | r <- xs, x < r ]
Haskell is purely functional
Functional = functions are first-class values
Pure = a program is an expression that evaluates to a value
no side effects!
unlike in Python, Java, etc:
in Haskell, a function of type
Int -> Int
Computes a single integer output from a single integer input Does nothing else
Referential transparency: The same expression always evaluates to the same value
Why is this good?
- easier to reason about (remember
x++
vs++x
in C++?) - enables compiler optimizations
- especially great for parallelization (
e1 + e2
: we can always computee1
ande2
in parallel!)
QUIZ
The function head
returns the first element of a list.
What is the result of:
A. Loops forever B. Type error C. 0
D. 1
Haskell is Lazy
An expression is evaluated only when its result is needed!
Why?
take 2 (goBabyGo 1)
=> take 2 (1 : goBabyGo 2)
=> take 2 (1 : 2 : goBabyGo 3)
=> 1: take 1 ( 2 : goBabyGo 3)
=> 1:2: take 0 ( goBabyGo 3)
=> 1:2: []
Why is this good?
can implement cool stuff like infinite lists:
[1..]
- encourages simple, general solutions
but has its problems too :(
That’s all folks!