Introduction To Programming in Haskell
Introduction To Programming in Haskell
UC Irvine
Adapted from John Hughes
Ericsson (telecommunications)
Carlstedt Research & Technology (air-crew scheduling)
Why Haskell?
Haskell is a very high-level language (many details taken care of automatically).
Haskell is expressive and concise (can achieve a lot with a little effort). Haskell is good at handling complex data and combining components. Haskell is not a high-performance language (prioritise programmer-time over computer-time).
Functional Programming
A function is a way of computing a result from the function arguments. A function producing a number from an angle. f(x) = sin x/cos x game(mouse clicks) = screen animation A function producing a sequence of images from a sequence of mouse clicks. A functional program computes its output as a function of its input.
Operations
Operators are always explicit: b^2 - 4*a*c
Power. Multiplication.
Cannot be written as
b2 - 4ac.
Functions
The solution of a quadratic equation:
A function.
area :: Int
area = 41*37 Names start with a small letter, and are made up of letters and digits.
area :: Int
area = 41*37
Function Definitions
A function definition specifies how the result is computed from the arguments. Function types specify the types of the arguments and the result: type signature declaration area :: Int -> Int -> Int
area l b = l*b The body specifies how the result is computed: declaration
Function Notation
Function arguments are not enclosed in brackets! Example: average :: Float -> Float -> Float
average x y = (x + y) / 2
Calls: average 2 3 2.5
6.5
Functional Programming
A functional program consists mostly of function definitions. Simple functions are used to define more complex ones, which are used to define still more complex ones, and so on. Finally, we define a function to compute the output of the entire program from its inputs. If you can write function definitions, you can write functional programs!
Some types are built in to programming languages (e.g. numbers), others are defined by programmers (e.g. MP3). Let us tour some of Haskells built-in types.
lists
User-Defined data-types.
The Num class provides several basic operations common to all numeric types
addition, (+) subtraction, (-) negation, negate Multiplication, (*) absolute value, abs others
declaration specifies
precedence level from 0 to 9 (with 9 being the strongest(default); normal application is assumed to have a precedence level of 10) left-(infixl), right(infixr), or nonassociativity (infix).
addition, (+) subtraction, (-) negation, negate Multiplication, (*) absolute value, abs others
Types: Integers
1, 2, 3, 4 :: Int Whole numbers (between -2^31 and 2^31-1).
Some operations:
2+3 2*3 5 6 div 7 2 mod 7 2 3 1
2^3
Some operations:
2.5 + 1.5 3 - 1.2 1.4142^2 4.0 1.8 1.99996
1/3
0.333333
sin (pi/4)
0.707107
Types: Lists
A list of values enclosed in square brackets.
A list of integers.
[1,2,3], [2] :: [Int]
Some operations:
[1,2,3] ++[4,5] head [1,2,3] last [1,2,3] [1,2,3,4,5] 1 3
Question
How would you add 4 to the end of the list [1,2,3]?
Quick Q...
How would you add 4 to the end of the list [1,2,3]? [1,2,3] ++ [4] [1,2,3,4]
Types: Strings
Any characters enclosed in double quotes. Some operations: Hello ++ World Hello World Hello! :: String The type of a piece of text.
show (2+2)
Question
Is 2+2 equal to 4?
Question
Is 2+2 equal to 4?
No!
2+2 is a string three characters long. 4 is a string one character long. They are not the same text!
Types: Commands
A command to write Hello! to myfile. The type of a command which produces no value.
Question
If myfile contains Hello!,
is readFile myfile equal to Hello!?
Question
If myfile contains Hello!,
is readFile myfile equal to Hello!?
NO!
The result of a function depends only on its arguments; Hello! cannot be computed from myfile.
Effects of Commands
The result of a function depends only on its arguments. The effect of a command may be different, depending when it is executed.
Take one step backwards is always the same command...
Types: Functions
double :: Int -> Int double x = x+x double 2 4
is a function call.
function value.
Function Composition
quadruple :: Int -> Int quadruple = double . double Function composition: an operator on functions! quadruple 2 double (double 2)
double 4
8
doubles [1,2,3]
Definitions Revisited
A definition double :: Int -> Int
double x = 2*x
makes a true statement about the function defined, (whatever x is, then double x and 2*x are equal) gives a way of computing calls of the function.
Question
Given the definition
x :: Int x*x = 4
Is x equal to 2?
Question
Given the definition
x :: Int x*x = 4
Is x equal to 2?
NO!
This is not a valid Haskell definition. It makes a true statement about x, but it does not give a way of computing x.
double 8
2*8
16
Evaluation Order
There may be more than one way to evaluate an expression:
Innermost Reduction eager evaluation 2*8 2*(3+5) Outermost Reduction laxy evaluation
NOTE: Haskell also has lazy data-structures. more on that later.
16
Evaluation Order
There may be more than one way to evaluate an expression:
three :: Integer->Integer three x = 3 infinity :: Integer-> Integer infinity = infinity +1
Innermost Reduction eager evaluation three (infinity+1) three infinity =3 three(infinity) Outermost Reduction laxy evaluation three((infinity + 1) +1)
three(((infinity +1)+1)+1).)
Sharing Evaluation
double :: Int -> Int double x = x+x double (3*5)
(3*5)+(3*5) double 15
15+(3*5)
15+15
30
Sharing Evaluation
Via Graph Reduction double :: Int -> Int double x = x+x double (3*5) (3*5)+(3*5) double 15 15+15 30
Haskell represents expressions as graphs so that duplicated subexpressions can be shared and reduced at most once. This is an implementation of PASS-BY-NAME semantics
NO! Haskell `remembers that both occurrences of 3*5 are really the same, and evaluates both in one step.
Sharing Evaluation
Via Graph Reduction double :: Int -> Int double x = x+x double (3*5) (3*5)+(3*5) double 15 15+15 30
Haskell represents expressions as graphs so that duplicated subexpressions can be shared and reduced at most once. This is an implementation of PASS-BY-NAME semantics
NO! Haskell `remembers that both occurrences of 3*5 are really the same, and evaluates both in one step.
Definition by Cases
Often programs must make decisions, and compute different results in different cases.
Example:
2<3
2 == 3
True
False True
Not equals.
2 /= 3
Example:
OR
max x y | x <= y = y
max x y | x > y = x
|x>y=x
A guard: an expression of type Bool. If the guard is True, the equation applies.
Recursion
Problem: define fac :: Int -> Int fac n = 1 * 2 * * n
What if we already know the value of fac (n-1)? = 1 * 2 * * (n-1) * n = fac (n-1) * n
Then fac n
A Table of Factorials
n 0 1 2 3 4 ... fac n 1 1 2 6 24 So fac 3 = 2 * 3. So fac 2 = 1 * 2. So fac 1 = 1 * 1.
Evaluating Factorials
fac :: Int -> Int
fac 0 = 1
fac n | n > 0 = fac (n-1) * n
fac 4
?? 4 == 0 ?? 4 > 0
False True
fac 2 * 3 * 4 fac 1 * 2 * 3 * 4
fac (4-1) * 4
fac 3 * 4
fac 0 * 1 * 2 * 3 * 4
1*1*2*3*4 24
Currying
Consider this add function: add :: Integer -> Integer -> Integer In other words, applying add to one argument yields add x y = x + y a new function which is then
applied to the second argument.
add e1 e2
(add e1) e2
Currying
add e1 e2
(add e1) e2
NOTE: operands associate left to the function, while results associate to the next right-most operand
Currying
A useful device for reducing the number of parentheses in an expression is the idea of replacing a structured argument by a sequence of simpler ones. Consider smaller (returns the smaller of 2 integers) UNCURRIED: smaller :: (Integer, Integer) Integer smaller(x, y) = if x <= y then x else y CURRIED: smallerc :: Integer (Integer Integer) smallerc x y = if x < y then x else y
smallerc 3 4 means (smallerc 3) 4
Currying
smaller curried: smallerc :: Integer (Integer Integer) smallerc x y = if x < y then x else y smallerc 3 4 means (smallerc 3) 4 Advantages of currying functions. help to reduce the number of parentheses curried functions can be applied to one argument only (giving another function that may be useful in its own right)
Operators
A sampling of Haskell infix built-in operators & their types: -- Functions: (->) Eq((==), (/=)), Ord(compare, (<), (<=), (>=), (>), max, min), -- Num((+), (-), (*), -- Integral(quot, rem, div, mod, quotRem, divMod), -- Fractional((/), recip) -- List (++) :: [a] -> [a] -> [a] [] ++ ys = ys (x:xs) ++ ys = x : (xs++ys)
Operators
A sampling of Haskell infix built-in operators & their types: Lambda Abstractions Instead of using equations to define functions, we can also define them anonymously" via a lambda abstraction. For example, In fact, the equations: inc x = x+1 add x y = x+y
Operators
A sampling of Haskell infix built-in operators & their types:
Infix Operators really just functions can be defined using equations lexically, infix operators consist entirely of symbols"
function composition (infix): (.) :: (b->c) -> (a->b) -> (a->c) f . g = \ x -> f (g x)
Sections
Remember all operators are just functions. Haskell allows the partial application of an infix operator: called sections. For example: (x+) = \y -> x+y (+y) = \x -> x+y (+) = \x y -> x+y [NOTE: The parentheses are mandatory.]
Sections
coerces an infix operator into an equivalent functional value handy when passing an infix operator as an argument to a function, ex: (What does this return?) map (+) [1,2,3] Examples Allows you to pass a function as an argument inc = (+ 1) add = (+) double = (* 2) First Class Object / pos = (>0) Higher Order Function half = (/2)
Lessons
Recursion lets us decompose a problem into smaller subproblems of the same kind -- a powerful problem solving tool in any programming language!
To ensure termination, define a `problem size which must be greater than zero in the recursive cases, and decreases by at least one in each recursive call.
Tuples
A tuple is a value made up of several other values, its components.
Examples: A point in the plane (x,y) A point in space (x,y,z)
A purchase
The null tuple
Tuple Types
A tuple type specifies the type of each component.
Examples:
Type Definitions
We can give names to types with a type definition:
type Purchase = (String, Int) All types start with a capital letter. Now Purchase and (String, Int) are interchangeable: dagens :: Purchase dagens = (Dagens Lunch, 55)
Pattern Matching
Functions on tuples can be defined by pattern matching:
name (s, i) = s
A pattern which must match the actual argument.
The type of a functions result is not allowed to depend on the values of the arguments.
Lists
A list is also a value made up of other values, its elements.
Examples:
[1, 2, 3]
[True, False, True] [] [(Dagens Lunch, 55)]
:: [Int]
:: [Bool] :: [Float] :: [Purchase]
List Comprehensions
We often do the same thing to every element of a list:
Example:
Filtering Lists
A list comprehension can include a condition which elements must satisfy. Include only those i which pass this test.
Example:
factors :: Int -> [Int]
factors 12
[2, 3, 4, 6, 12]
Using factors
Counting the number of factors numFactors :: Int -> Int numFactors n = length (factors n) Testing for a prime number isPrime :: Int -> Bool isPrime n = factors n == [n] The number of elements in a list.
<-
<- [2..n]
|i
<- [2..n]
[ f x | x <- xs ]
| i <- [2..n], n `mod` i == 0] Write a comma and a condition at the end of the comprehension.
[i
quicksort [] = [] quicksort (x:xs) = quicksort [y | y <- xs, y<x ] ++ [x] ++ quicksort [y | y <- xs, y>=x]
But not always! Recursion is a powerful and general tool -therefore harder to use than special purpose tools, when they are applicable.
Lists are not always efficient: candidates for replacement by faster structures later in program development.
But first:
Polymorphic Types
length :: [Int] -> Int length :: [Float] -> Int length :: [Card] -> Int
For any type t, length :: [t] -> Int A type variable, which may stand for any type.
Examples:
take 3 (factors 12) drop 3 (factors 12) [2, 3, 4] [6, 12]
Question
take and drop have the same (polymorphic) type. What is it?
Question
take and drop have the same (polymorphic) type. What is it?
take, drop :: Int -> [a] -> [a] A must stand for the same type everywhere. Examples: Int -> [Float] -> [Float] Int -> [String] -> [String] Not Int -> [Float] -> [String]
The Zipper
Often we want to combine corresponding elements of two lists:
John , Simon, Mary ]
zip [
Welsh,
English,
Irish ]
[ (John,Welsh),
(Simon,English), (Mary,Irish)]
Example:
Idea: Mark every element with its position, and search for the one we want.
[ 1,
2,
3]
Rule of Thumb
Question: Do I want to go through the elements of two lists together? Answer: Use zip!
Lessons
Lists model any kind of sequence in the real world.
Lists can express many kinds of repeated computation in programs. Special constructions and a rich set of polymorphic standard functions let us manipulate lists very easily indeed. Look for opportunities to use them!
Types in Haskell
Programming Languages: ICS 141 UC Irvine
Strong Typing
Haskell is a typeful programming language computations evaluate expressions yield values Each value has an associated type.
Values Types
First class objects passed as arguments returned as results placed in data structures, etc
NOT first-class
attribute of a value
Strong Typing
TYPE SAFE:
Static Type System Ensures that Haskell programs are type safe; i.e. no programmer mismatches
Basic Types
Int : finite! Integer: infinite enumeration. Type Rules for
Tuples
For (e1,e2, ,en); n >= 2, if ti is the type of ei, then the type of the tuple is (t1,t2, : : : ,tn).
Lists
For [e1,e2, ,en]; n >= 0, each ei must have the same type t, and the type of the list is [t].
data Color = Red | Green | Blue | Indigo | Violet Bool & Color: Enumerated Types
consist of a finite number of nullary data constructors.
Pattern Matching
Semantics Pattern matching can either fail, succeed or diverge
(done top-to-bottom, left-to-right)
Matching is permitted using the constructors of any type, userdefined or not. Nesting of patterns is permitted to arbitrary depth.
Example:
contrived :: ([a], Char, (Int, Float), String, Bool) -> Bool contrived ([], 'b', (1, 2.0), "hi", True) = False
Wild-cards:
head (x:_) = x tail (_:xs) = xs
PATTERN MATCHING DIFFERENCE BETWEEN PROLOG & HASKELL HASKELL: one-way" matching, PROLOG: two-way" matching (via unification), & backtracking.
Polymorphic Types
Types that are universally quantified in some way over all types. Polymorphic type expressions essentially describe families of types. (a) [a] is the family of types consisting of, for every type a, the type of lists of a. i.e. Lists of integers (e.g. [1,2,3]) Lists of characters (e.g. ['a','b','c']) Lists of Lists of integers (e.g. [[1], [2] , [3]]) Etc
is it a valid example?
NOTE: Tree is a type constructor Branch and Leaf are data constructors. Suppose we wish to define a function fringe that returns a list of all the elements in the leaves of a tree from left to right. It's usually helpful to write down the type of new functions first; in this case we see that the type should be Tree a -> [a].
fringe :: Tree a -> [a] fringe (Leaf x) = [x] fringe (Branch left right) = fringe left ++ fringe right
Miscellaneous
Types begin with Upper case, identifiers with lower, & Haskell is case sensitive. Comments
To end of line: the characters -- and all subsequent characters to the end of the line are ignored. Embedded or Multi-Line: {--}
Modules
Creating Modules Importing Modules
Haskell's Standard Prelude (in Appendix A of the Report and the standard libraries (found in the Library Report [5]) contain lots of useful examples of Haskell code In order to use the functions created in the modules, i.e. List, etc it is required that you import List