PowerPoint Presentation
Haskell!
Intro to Haskell
Named after logician Haskell Curry (1900-1982)
A more modern functional language: created late 1980’s
Many similarities with Racket:
Programming with expressions, not statements
– No mutation! Everything is immutable.
Functions are values
– Can be passed as parameters to and returned from functions
Lists are the fundamental data structure
2
Intro to Haskell
Named after logician Haskell Curry (1900-1982)
A more modern functional language
Key distinctions from Racket:
Lazy Evaluation
An Extensive Type System
3
Scheme
Intro to Haskell
Named after logician Haskell Curry (1900-1982)
A more modern functional language
Key distinctions from Racket:
Lazy Evaluation
An Extensive Type System
Cleaner syntax
Notation even closer to mathematics
Infinite lists
Pattern matching
Purely functional
6
How do we write functions in Haskell?
With a more minimal syntax than Racket:
triple x = x * 3
[1] [2][3] [4]
[1] name of the function (note: camelCase)
[2] parameters, separated only by whitespace
[3] = binds the name to the definition and cannot be re-bound; pronounced “is”: “triple x is x times 3.”
[4] body of function (note: infix notation)
Actually, Haskell operator notation is infix or prefix
Infix notation tends to be the default preference:
5*(4+6)-2 — evaluates to 48
5*4^2-2 — evaluates to 78
… but you can also force prefix notation by enclosing operators
in parentheses:
(-) ((*) 5 ((+) 4 6)) 2
Racket
(define (identity x) x)
(define (add-one x)
(+ x 1))
(define (square x)
(* x x))
(define (add-stuff-to-five a b)
(+ a b 5))
(define (add-stuff x)
(lambda (y) (+ x y))
Haskell
identity x = x
addOne x = x + 1
square x = x * x
addStuffToFive a b = a + b + 5
addStuff x y = x + y
WHAT THE…???
Racket
Returning a lambda function is clearly possible in Racket, but must be explicitly coded.
Haskell: Every function is “curried” by default
Currying
Currying is the process of transforming a function that takes multiple arguments, into a function that takes just a single argument and returns another function which accepts further arguments, one by one.
All functions in Haskell take just one argument. But looking at function notation, this fact is not necessarily apparent. For example, take a simple division function:
div x y = x / y
div 11.0 2.0
5.5
This looks like a function definition that takes two arguments, x and y, at the same time.
But in fact, Haskell sees (div 11.0 2.0) as a two-part process!
(div 11) is, by itself, a meaningful evaluation for Haskell. It yields:
\y -> 11 / y “A new procedure (lambda) that takes a y and returns
11 divided by y”
(div 11.0 2.0) is more properly ((div 11.0) 2.0) 5.5
The Lambda Calculus rears its head again…
In the LC, all definitions with multiple arguments take those arguments one at a time:
λfunc.λarg.(func arg)
This function takes one argument (func), then returns a new function that takes one argument (arg), then returns the application of the first argument to the second argument.
Haskell purely implements the lambda calculus.
As in the lambda calculus, all functions in Haskell take one argument and return one result. The way to think of this is that, in Haskell, when it seems we are passing multiple arguments to a function, we are actually applying a series of nested functions, each with one argument. In Haskell, this is called “currying.”
Haskell:
Which of the following functions are equivalent?
func x y z = x * y * z
func x y = \z -> x * y * z
func x = \y -> \z -> x * y * z
func = \x -> \y -> \z -> x * y * z
All four are equivalent.
addStuffToFive a b = a + b + 5
f = addStuffToFive 6
\b -> 6 + b + 5
b.(6 + b + 5)
f 10
21
But why take only one argument and return a new function that expects a new argument, rather than accept all parameters up front??
This kind of higher-orderism is required by built-ins like map/filter/fold
It also enables code re-use
Racket:
(define (add-stuff-to x) (lambda (y) (+ x y))
(map (add-stuff-to 5) (list 1 2 3)) (list 6 7 8)
Haskell:
addStuff x y = x + y
map (addStuff 5) [1, 2, 3] [6, 7, 8]
Remember our revenue program in Racket?
Even tinier in Haskell
Partial Application
When a function is purposefully supplied with fewer arguments than it expects, we say that it is “partially applied”:
add x y = x + y
> add 1
Again, because Haskell functions are curried by default, partial application automatically creates a new function that is ready to accept the remaining argument(s):
add x y = x + y > add 1
add = \x -> \y -> x + y \y -> 1 + y
If you like, you can think of partial application as “preloading” a function with some of its arguments.
Hmm, but Haskell function notation lets me at least pretend that arguments are passed in all at once. So, can I just ignore this one-at-a-time business?
Nope. Haskell has a strict type system, which strongly recommends lambda calculus-style type signatures to be declared above every function.
What is a type signature?
Up until this slide, we’ve omitted the type signature. But in fact, a Haskell function is defined by writing its type signature on the first line, and its parameters and body on the remaining lines.
A type signature tells the compiler the expected types of the arguments to, and the type of the returned value of, a function.
max3 :: Int -> Int -> Int -> Int
max3 x y z
| x>=y && x>=z = x
| y>=x && y>=z = y
| otherwise = z
Sometimes you will also see “type signature” written as “type declaration.”
If the type signature is omitted, the Haskell compiler will infer it.
Functions are polymorphic: they can use variables of different types at different times
Omitting the type signature gives the function its broadest possible meaning
FOR EXAMPLE:
max3 x y z
| x>=y && x>=z = x
| y>=x && y>=z = y
| otherwise = z
> max3 6 4 1
6
> max3 “alpha” “beta” “gamma”
“gamma”
The type that Haskell infers for max3 is Ord a => a -> a -> a -> a
Providing type signatures is a “best practice”
Haskell’s type system clarifies thinking and expresses program structure
Writing type signatures serves as a form of code documentation
Type ambiguity is possible. But providing type signatures turns run-time errors into compile-time errors.
Type Signatures
identity :: t -> t
identity x = x
increment :: Int -> Int
increment x = x + 1
plus :: Num a => a -> a -> a
plus x y = x + y
filter :: (a -> Bool) -> [a] -> [a]
filter
map :: (a -> b) -> [a] -> [b]
map
25
Recommended Reading:
“Learn You a Haskell for Great Good”
Please do invest time in this resource,
it has great explanations
Match the function to its type signature
not
length
concat
head
(<) __ :: [a] -> a
__ :: [[a]] -> [a]
__ :: Bool -> Bool
__ :: [a] -> Int
__ :: Ord a => a -> a -> Bool
Match the function to its type signature
head :: [a] -> a
concat :: [[a]] -> [a]
not :: Bool -> Bool
length :: [a] -> Int
(<) :: Ord a => a -> a -> Bool
Participation Quiz:
Match the Haskell function
with its type signature
Function Type Signature Description
“Cons (:) takes an element of type a and then returns a function that takes
a list of elements of type a, and then returns a new list of elements of type a.”
Heads Up: Midterm
One month away (Wednesday, March 24th)
Upcoming schedule:
Two weeks of Haskell
Spring Break
Midterm review day after we return from break
Midterm covers Functional Programming in…
Racket
Lambda Calculus
Haskell
/docProps/thumbnail.jpeg