The HaskellProgramming Language
University of Washington, Seattle
(C) 1999, Greg J. Badros—All Rights Reserved
An imperative language
- Can we substitute i for 7 in the printf?
Scheme is still imperative
- Can we substitute 7 for i?
A more realistic example
(map + (window-size) (window-position))
(map + (window-size) (window-position))
- Want to factor out the duplicated code!
Factoring out an expression
(map + (window-size) (window-position))]
- Works as long as window-size, window-position always return same value, and no set! procedures affecting se-corner
Haskell:Works with fewer ifs and buts
- Almost no restrictions on when you can move expressions around without changing the meaning of the code
- Haskell permits “equals to be substituted for equals”
- This is called referential transparency
Overview of Haskell
- Purely functionalNo set!, no assignment— only bindings
- Fewer parentheses, more special syntax
- Static, polymorphic types
- Lazy evaluation of arguments
Haskell’s History
- Designed by committee in 1987
- Named after logician Haskell B. Curry
- Miranda is purely functional also
- Evolved into Haskell 1.4, Haskell 98
- Especially popular in Great Britain, Australia
Used as introductory programming language!
The brevity of Haskell
q (x:xs) = q [y | y <- xs, y<x ]
- Recognize this procedure?
Read as in mathematical notation:
list of values y, drawn from list xs, with y>=x
Quicksort in two lines!
quicksort (x:xs) = quicksort [y | y <- xs, y<x ]
++ quicksort [y | y <- xs, y>=x]
Haskell syntax
- More syntactic features than Scheme
- Generally more concise than Scheme
- Both infix operators and functions
- Whitespace and indentation can matter
- Very readable code,but sometimes harder to write
- Sometimes quirky precedence rules
Use parentheses when in doubt, e.g. (-9)
Syntax:Haskell vs. Scheme
Hugs98Haskell Users Gofer System
- Nearly complete implementation of Haskell98
- Less fancy than Dr. Scheme—basically just a repl (read eval print loop)
- Commands to the interpreter beginwith “:”; e.g. :?, :q, :load, :reload
- Other lines are treated asexpressions to evaluate
One gotcha of Hugs98
- Files must be used to hold the definitions of functions that you write
You cannot define functions at the repl!
- Use a separate text editor to create and edit those files
- Then :load the file in and use the functions you defined
Learning Haskell
- Generate hypotheses, test them,re-evaluate the hypotheses based on the evidence you collect
Some simple expressions
"foo" ? "foo" string “foo”
[1,2,3]++[4,5] ? [1,2,3,4,5] list of nums
1:[2,3,4] ? [1,2,3,4] list of nums
let x = 2 in x*3 ? 6 number 6
When things do notgo according to plan...
ERROR: Unresolved overloading
*** Type : (Num a, Num [a]) => [a]
- Haskell does not allow improper lists
More errors...
ERROR: Type error in application
- Haskell does not allow heterogeneous lists—all elements must be same kind of value
A simple example
-- length is built-in… this is just an example
length (x:xs) = 1 + (length xs)
- Looks like we have defined the function “length” twice…
- Not really—just using pattern matching
Fibonnaci numbersMathematical notation
otherwise f(n-1) + f(n-2)
Fibonnaci numbers in HaskellTwo different approaches
fib n = fib (n-1) + fib (n-2)
| otherwise = fib (n-1) + fib (n-2)
Indentation matters!
ERROR "l.hs" (line 10): Syntax error in declaration (unexpected `;', possibly due to bad layout)
- ; and { } can be used as explicit grouping constructs
- Haskell’s “layout” syntax is two-dimensional
- ; and { } are inserted automatically based on code indentation
Implicit vs. explicit grouping
-- rewritten internally to:
Explicit groupingis useful at the repl
- repl only accepts a single line of input
- Use: let x = 1; y =2 in x+y
- There’s more than one way to do it(TMTOWTDI):x+y where x=1; y=2
Function application
- We wrote (proc arg1 arg2 …) in Scheme
- Haskell uses “juxtaposition”—writing the argument next to the name of the procedure:f x yorf (x,y)
Functions withmultiple arguments
- Two ways to define mymul:mymul_c x y = x * ymymul (x,y) = x * y
Takes one argument that is a tuple
Currying
This function could be written:
Partial application
mymul_c 5 2 ? (mymul_c 5) 2
- Name “specializations” via partial application—omit trailing argument(s):
Specializations
- Consider map::type map ? :: (a -> b) -> [a] -> [b]
- Specializations let us bind leading arguments, giving them pre-specified values:negator = map negate:type negator ? :: [Integer] -> [Integer]negator [1,2,3] ? [-1,-2,-3]
Lambda expression syntax
- \ (looks most like ?) introduces anonymous function
\ arg1 arg2 arg3 -> expression
Functions vs. operators
- mymul is a function, * is an operatormymul 2 3 -- prefix for function application-- but2 * 3 -- infix for operator application
- Can use function names as operators…2 `mymul` 3
Other operators
^ raise to the power (e.g., 2^3 ? 8)
`div` whole number division
`mod` whole number remainder
. function composition (f . g) args = f(g(args))
Why use tuples for arguments?
- Sometimes the argument really is a tuple
- Pattern matching can let you decompose the arguments from inside a tuple:-- suppose multiplication were a-- very expensive operation...mymul (x,0) = 0mymul (0,y) = 0mymul (x,y) = x * y
Ackermann function(Wilhelm Ackermann, 1928)
- Grows really quickly—A(4,2) is about 1019728(but do not try to compute even A(4,1)!)
Patterns
- Used to match structural properties of values (often arguments)
- First (from top to bottom in program text) matching pattern to succeed provides the definition-- return first n elements of itemstake 0 items = []take n [] = []take n (x:xs) = x : take (n-1) xs
Wildcards in patterns
mymul (_,0) = 0-- another example
Just a placeholder, makes no binding
Patterns and case expressions
- Patterns are just sugar for a more general case expressiontake m ys = case (m,ys) of (0,_) -> [] (_,[]) -> [] (n,x:xs) -> x : take (n-1) xs
Conditionals
- Patterns have eliminated much of the need for conditionals!
- Still have if-then-else expression syntax:if test then exprA else exprB
- Just sugar for:case test of True -> exprA False -> exprB
List shorthands
[0.0,0.3 .. 1.0] ? [0.0,0.3,0.6,0.9]
- Even can make infinite lists!
[1,2..] ? [1,2,3,4,5,6,7,…]
[1,3..] ? [1,3,5,7,9,11,…]
List comprehension
[x | x <- xs, xɛ ] -- all xɛ from list xs
[ 2*x | x <- xs ] -- double elements in xs
-- double elements y<x from list xs
-- double even elements y<x from list xs
[ 2*x | x <- xs, xɛ, isEven x ]
Compare to usinghigher order functions
[x | x <- xs, xɛ ] ? filter (\y -> y<x) xs
[ 2*x | x <- xs ] ? map (\x -> 2*x) xs
[ 2*x | x <- xs, xɛ ] ?map (\x -> 2*x) (filter (\x -> x < 3) xs)
[ 2*x | x <- xs, xɛ, isEven x ] ?map (\x -> 2*x) (filter (\x -> x < 3 && isEven x) xs)
Comprehension insteadof nested loops
[ (x*2,y) | x<-[1..2], y<-[1..3] ]
? [(2,1),(2,2),(2,3),(4,1),(4,2),(4,3)]
[ (x*2,y) | y<-[1..3], x<-[1..2] ]
? [(2,1),(4,1),(2,2),(4,2),(2,3),(4,3)]
y is the inner loop “index”
x is the inner loop “index”
Values have types
('a',2.0) ? ('a',2.0) :: (Char,Double)
Ask Hugs toreport types of values
Functions have types too
length ? <<function>> :: [a] -> Int
(*) ? <<function>> :: Integer -> Integer -> Integer
head ? <<function>> :: [a] -> a
map ? <<function>> :: (a->b) -> [a] -> [b]
Built in types
- Int vs. Integer
- Int is fixed-precision integer
- Integer is arbitrary precision integer
- Double vs. Float
- Double is double-precision
- Float is single precision
Dynamic vs. Static typing
- Scheme used dynamic types:(define (f y) (* y "hi"));;; No error in above definition(f 5) ? Error: expects type <number> as second arg.
- Haskell catches the error earlier:f y = y * "hi" ?
Instance of Num [Char] required for definition of f
Advantages of static typing
- Find errors in untested code
- Avoid run-time argument checking
- code that has passed the “type-checker” is guaranteed to have no improper applications (such as multiplying a number by a string)
- code executes faster
- Better program documentation
Static typing rules
Application rule
If f has type s->t and e has type s
then f can be applied to e, and the result
Cancellation rule
If f has type t1->t2->…->tn->t and
ek has type tk, where k ? n
then f can be applied to e1, …, ek
and the result, f e1 … ek, has type
tk+1 -> tk+2 -> … -> tn -> t
Function rule
If x has type s and e has type t,
then \ x -> e has type s->t.
List rule
then [e1,…,ek] has type [t].
- Only homogeneous lists are permitted
Tuple rule
If e1 has type t1, …, ek has type tk
then (e1, …, ek) has type:
Length of list of integers
len :: [Int] -> Int -- you can declare a type
len ? <<function>> :: [Int] -> Int
Applying len to [Char]
ERROR: Type error in application
*** Expression : len ['a','b']
*** Does not match : [Int]
What does len rely on?
- All that matters is that the argument is a list of something
- With above definition:len ? [a] -> Integer
Type variables
head ? <<function>> :: [a] -> a
map ? <<function>> :: (a->b) -> [a] -> [b]
“a” stands for any typethe type is a template for many types
Polymorphic functions
- length can operate on any kind of list ([a]) and return an Integer
- Polymorphic = many shaped
- Many functions in Haskell are polymorphic
- You can (and do!) write polymorphic functions too
Type inference
- You often need not specify types
- Haskell looks inside your function and figures it out:f (x,y) = (x, ['n' .. y])f :: (a, Char) -> (a, [Char])
Type unification
- Suppose we want h = g . f(the output of f is applied as input to g) g f(Int, [b]) -> Int (a, Char) -> (a, [Char])
- Must “unify” types:(a, [Char]) and (Int, [b])
Unification is the intersection of the described types
Evaluation in Haskell
- Recall Scheme:(multiply (sum 1 2) (sum 3 4)) ? (multiply 3 7) ? (* 3 7) ? 21
- multiply (sum 1 2) (sum 3 4) ? (sum 1 2) * (sum 3 4) ? 3 * 7 ? 21
Not all arguments getevaluated
- In Scheme:(car (list (+ 1 2) (/ 5 0))) ? ERROR!
- In Haskell:head [1+2, 5/0] ? 3.0-- only evaluate the arguments we use
- No error, because we discard the second element of the list before using it!
Eager evaluation vs.lazy evaluation
- Eager evaluation — “applicative-order”
- Proceeds “inside-out”
- All arguments are evaluated beforefunction is applied
- Lazy evaluation
- Proceeds “outside-in”
- Arguments are passed in to function as full expressions
Lazy evaluationis efficient
(factorial 6) * (factorial 6) ?
- factorial 6 is onlyinvoked once!
No need for special forms
myif True exprTrue _ = exprTrue
myif False _ exprFalse = exprFalse
- Only one of the two expressions gets evaluated
- Remember, since Haskell has no side effects, this is simply a matter of efficiency
Outside-in evaluation avoids intermediate lists
sumFourthPowers n = sum (map (^ 4) [1 .. n])
sumFourthPowers 3 ?sum (map (^ 4) [1..3]) ?sum ((^ 4) 1 : map (^ 4) [2..3])) ?(1^4) + sum (map (^ 4) [2..3])) ?1 + sum (map (^ 4) [2..n]) ?…1 + (16 + (81)) ? 98