810 likes | 944 Views
A Lightning Tour of Haskell. Lecture 1, Designing and Using Combinators John Hughes. Using Haskell: The Hugs Interpreter. Using Haskell: The Hugs Interpreter. A module is loaded. Using Haskell: The Hugs Interpreter. A module is loaded. Type an expression at the prompt.
E N D
A Lightning Tour of Haskell Lecture 1, Designing and Using Combinators John Hughes
Using Haskell: The Hugs Interpreter A module is loaded.
Using Haskell: The Hugs Interpreter A module is loaded. Type an expression at the prompt.
Using Haskell: The Hugs Interpreter A module is loaded. Type an expression at the prompt. The value is printed.
Using Haskell: The Hugs Interpreter A function call with two arguments. No brackets! Brackets are only for grouping e.g. f (g x)
Using Haskell: The Hugs Interpreter A linked list 1 3 5 1 : 3 : 5 : []
Defining Functions insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs
Defining Functions Type signature. Optional! insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs
Defining Functions Type signature. Optional! Ignore for now insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs
Defining Functions Type signature. Optional! Ignore for now Type of first argument: “a” insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs
Defining Functions Type signature. Optional! Ignore for now Type of first argument: “a” insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Type of second argument: list of “a”s
Defining Functions Type signature. Optional! Ignore for now Type of first argument: “a” Type of result insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Type of second argument: list of “a”s
Defining Functions Type signature. Optional! Ignore for now Type of first argument: “a” Type of result insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Type of second argument: list of “a”s What is “a”? A type variable which can stand for any type. This function is polymorphic.
Defining Functions insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Definition by “pattern matching”: case analysis on the form of the arguments.
Defining Functions insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Definition by “pattern matching”: case analysis on the form of the arguments. “Guards” define conditions for an equation to apply.
Defining Functions We build a new structure as the result: “purely functional”. insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Definition by “pattern matching”: case analysis on the form of the arguments. “Guards” define conditions for an equation to apply.
Defining Data Types data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show
Defining Data Types Type name data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show
Defining Data Types Type name Type parameter data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show • Types may take parameters. • Enables us to define polymorphic functions which work on a tree with any type of labels.
Defining Data Types Type name Type parameter data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show Constants start with upper case, variables with lower • Types may take parameters. • Enables us to define polymorphic functions which work on a tree with any type of labels.
Defining Data Types Node and Leaf are alternative forms of Tree. data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show
Defining Data Types Node and Leaf are alternative forms of Tree. data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show Types of the components.
Defining Data Types Node and Leaf are alternative forms of Tree. data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show Ignore for now. Types of the components.
Defining Data Types data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show
Defining Data Types data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show Type: Tree Integer
Tree Insertion insertTree :: Ord a => a -> Tree a -> Tree a insertTree x Leaf = Node x Leaf Leaf insertTree x (Node y l r) | x < y = Node y (insertTree x l) r | x > y = Node y l (insertTree x r) | x==y = Node y l r Pattern matching works as for lists.
Overloading • Polymorphic functions use the same definition at each type. • Overloaded functions may have a different definition at each type. Class name. Read: “a is a type in class Eq, if it has the following methods”. class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x/=y = not (x==y) Class methods and types. Default definition.
The Class Hierarchy Read: “Type a in class Eq is also in class Ord, if it provides the following methods…” class Eq a => Ord a where (<) :: a -> a -> Bool …
Instance Declarations instance Eq Integer where x==y = …primitive… instance Eq a => Eq [a] where [] == [] = True x:xs == y:ys = x == y && xs == ys Provided a is in class Eq, then [a] is in class Eq, with the method definition given.
Types of Overloaded Functions “a” may be any type in class Ord. insert :: Ord a => a -> [a] -> [a] insert x [] = [] insert x (y:xs) | x<=y = x:y:xs | x>y = y:insert x xs Because insert uses a method from class Ord.
Show and Read class Show a where show :: a -> String class Read a where read :: String -> a These are simplifications: there are more methods in reality. read . show = id (usually)
Derived Instances data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show Constructs a “default instance” of class Show. Works for many standard classes. Main> show (Node 1 Leaf (Node 2 Leaf Leaf)) "Node 1 Leaf (Node 2 Leaf Leaf)"
Multi-Parameter Classes Define relations between classes. class Collection c a where empty :: c add :: a -> c -> c member :: a -> c -> Bool c is a collection with elements of type a. instance Eq a => Collection [a] a where empty = [] add = (:) member = elem instance Ord a => Collection (Tree a) a where empty = Leaf add = insertTree member = elemTree
Functional Dependencies A functional dependency class Collection c a | c -> a where empty :: c add :: a -> c -> c member :: a -> c -> Bool • Declares that c determines a: there can be only one instance for each type c. • Helps the type-checker resolve ambiguities (tremendously). add x (add y empty) -- x and y must be the same type.
“Side Effects” in Haskell • Suppose • tick :: String -> Integer • reads an integer n from a file with given name, • writes n+1 back to the file • returns n Then tick == tick might be False! Cannot replace equals by equals. Not “purely functional”!
Haskell’s Solution Performs I/O and delivers a String. Side effects are recorded in the type! readFile :: String -> IO String writeFile :: String -> String -> IO () So the type of tick is tick :: String -> IO Int and tick == tick is ill-typed. IO is a monad -- more later!
The do notation I/O actions may be combined in sequence. tick :: String -> IO Integer tick f = do contents <- readFile f let n = read contents writeFile f (show (n+1)) return n
The do notation I/O actions may be combined in sequence. Type: IO String tick :: String -> IO Integer tick f = do contents <- readFile f let n = read contents writeFile f (show (n+1)) return n Type: String Scope of contents
The do notation I/O actions may be combined in sequence. tick :: String -> IO Integer tick f = do contents <- readFile f let n = read contents writeFile f (show (n+1)) return n Local declaration Scope
The do notation I/O actions may be combined in sequence. tick :: String -> IO Integer tick f = do contents <- readFile f let n = read contents writeFile f (show (n+1)) return n Type: IO () No need to bind a name to the result.
The do notation I/O actions may be combined in sequence. tick :: String -> IO Integer tick f = do contents <- readFile f let n = read contents writeFile f (show (n+1)) return n Type: IO Integer Defines result of tick. return :: a -> IO a
IO a = Action Yeilding an a Action at the prompt is performed. The action returned by tick has a side-effect. Yields a different Integer each time it is performed.
IO a = Action Yeilding an a twice1 :: IO a -> (IO a, IO a) twice1 c = (c,c) Result is not an action! Therefore not performed. Next call reveals no side-effects occurred.
IO a = Action Yeilding an a twice2 :: IO a -> IO (a,a) twice2 a = do x <- a return (x,x) twice3 :: IO a -> IO (a,a) twice3 a = do x <- a y <- a return (x,y) The same action can be performed many times.
References Variables in Haskell cannot be updated -- references can. newIORef :: a -> IO (IORef a) readIORef :: IORef a -> IO a writeIORef :: IORef a -> a -> IO () Reference operations have side-effects -- hence IO type.
Example: Destructive List Insertion Updateable tail. data RList a = Nil | Cons a (IORef (RList a)) insertRList :: Ord a => a -> IORef (RList a) -> IO () insertRList x xs = do cell <- readIORef xs case cell of Nil -> do new <- newIORef Nil writeIORef xs (Cons x new) Cons y xs' | x<=y -> do new <- newIORef cell writeIORef xs (Cons x new) | x>y -> insertRList x xs' Must read the list cell. Create new cell and update old. case is inline pattern matching.
Encapsulated Side Effects • IORefs can only be updated at the top level. • Can we use references internally to define a pure function? • Example • removeDuplicates :: Hashable a => [a] -> [a] Array operations resemble reference ones. Use a hash table internally to make comparison fast. No IO type: no externally visible side-effects!
Encapsulation: The ST Monad newSTRef :: a -> ST s (STRef s a) readSTRef :: STRef s a -> ST s a writeSTRef :: STRef s a -> a -> ST s () Similar family of operations.