310 likes | 404 Views
(1). What are the types of the following values?. Exercises. [’a’,’b’,’c’] (’a’,’b’,’c’) [(False,’0’),(True,’1’)] ([False,True],[’0’,’1’]) [tail,init,reverse]. (2). (3). What are the types of the following functions?. Check your answers using Hugs. second xs = head (tail xs)
E N D
(1) What are the types of the following values? Exercises [’a’,’b’,’c’] (’a’,’b’,’c’) [(False,’0’),(True,’1’)] ([False,True],[’0’,’1’]) [tail,init,reverse] K. Louden, Programming Languages
(2) (3) What are the types of the following functions? Check your answers using Hugs. second xs = head (tail xs) swap (x,y) = (y,x) pair x y = (x,y) double x = x*2 palindrome xs = reverse xs == xs twice f x = f (f x) K. Louden, Programming Languages
Curried Functions mult:: Int -> Int -> Int mult x y = x * y • We could read this as a function taking two Int arguments and returning a third. OR-- a function taking a single Int argument and returning a function from Int -> Int as a result. • Moral: the mult function can take either one or two parameters; if you give it one, it returns a function of the (missing) 2nd parameter • Ex: map (mult 3) [1,2,3] Yields [3,6,9] K. Louden, Programming Languages
Curried Functions • Contrast the previous definition of mult with a definition using a tuple: mult2 :: (Int,Int) -> Int mult2 (x,y) = x *y • Now we must always supply both parameters at every call:The mult function is Curried (named after Haskell B. Curry), the mult2 function is not. K. Louden, Programming Languages
Curried Functions • Definition: A function taking multiple parameters is Curried if it can be viewed as a (higher-order) function of a single parameter. • Currying is good, since all functions can be viewed as having just a single parameter, and higher-order functions can be obtained automatically. K. Louden, Programming Languages
Haskell • A fully-Curried lazy purely functional language with Hindley-Milner static typing. (Fully-Curried means all functions, including built-in arithmetic, are implicitly Curried.) • Has many other features that make it perhaps the most advanced functional language available today. • Includes overloading and a built-in mechanism for generator-filter programming (called list comprehensions). K. Louden, Programming Languages
Sample Haskell code: fact n = if n == 0 then 1 else n * fact (n-1) square x = x * x pi1 = 3.14159 gcd1 u v = if v == 0 then u else gcd1 v (mod u v) squarelist lis =if (null lis) then liselse square (head lis): squarelist (tail lis) K. Louden, Programming Languages
Haskell properties • Parentheses are only necessary for grouping. • Semicolons are usually unnecessary. • Definitions have no special syntax, so they must be loaded from a file—they cannot be entered directly into an interpreter. • Operators are infix. • Lists are written using square brackets and commas (e.g. [1,2,3]), with ++ as the list append operation, and : (colon) as the list constructor operation. • All value and function names must start with a lowercase letter, and all types are uppercase: Int. • Names cannot be redefined unless you state you are overriding them - hence the use of pi1 & gcd1 in the previous slide (pi & gcd are predefined). K. Louden, Programming Languages
Haskell properties (2) • Haskell is purely functional, so there are no variables or assignments, and I/O and sequencing cannot be done except using special tools, called monads (which we do not study here). • Of course, there are still local definitions (in other words no value is being stored, we are just defining the pattern):let x = 2; y = 3 in x + yor: let x = 2 y = 3 in x + y • Note indentation in the previous code to get rid of the semicolon: Haskell uses a two-dimensional Layout Rule to remove extra syntax. Leading white space matters! K. Louden, Programming Languages
Haskell properties (3) Note lambda syntax • All local definitions are recursive:f x = let z = x-1 g = \y->if y==0 then z else g(y-1) in g x + 2 • All expressions are delayed in Haskell: ones = 1:ones -- can also write [1,1..] ints_from n = n : ints_from (n+1) ints = ints_from 1 -- also: [1..] • Infix operators can be made prefix by putting parentheses around them:doubleIt lis = map ((*) 2) lis Comment K. Louden, Programming Languages
Haskell properties (4) • Hindley-Milner type checking is performed on all definitions and expressions. Type variables use the names a, b, c, etc. • Types are not automatically displayed, but can be viewed by using the :t command in the interpreter. Small sample interpreter session:> [1]++[2][1,2]> :t (++)(++) :: [a] -> [a] -> [a]> [1]++['a'] – wants types to match error:No instance for (Num Char) arising from the literal `1' at <interactive>:1:1 Probable fix: add an instance declaration for (Num Char) In the list element: 1 In the first argument of `(++)', namely `[1]' In the definition of `it': it = [1] ++ ['a'] K. Louden, Programming Languages
Strong typing • Checks whether or not expressions we wish to evaluate or definitions we wish to use obey typing rules without any evaluation taking place. • A pattern is consistent with a type if it will match some elements of that type. • A variable is consistent with any type • A pattern (t:ts) is consistent with [p] if t is consistent with p and ts is consistent with [p] K. Louden, Programming Languages
Type Checking f:: a->b->c f x y | g1 = e1 | g2 = e2 |otherwise = e3 We must check • g1 and g2 are boolean • x is consistent with a and y is consistent with b • e1,e2,e3 are all of type c. K. Louden, Programming Languages
Examples of type inference • f (x,y) = (x,[‘a’..y]) • What is the type of f? • The argument of f is a pair. We consider separately the constraints of x and y. Y is used with [‘a’..y] so it must be a Char. • f :: (a, Char) -> (a, [Char]) K. Louden, Programming Languages
Examples of type inference • g(m,z) = m + length z • What constraints are placed on m and z? M must be numeric, as used with +. z must be a list as used with length. Furthermore, since length returns an int, we assume m is also an Int. • Now consider the function composition • The input of g must be the output of f • g . f :: (Int, Char) -> Int • *Main> K. Louden, Programming Languages
Unification • We describe the intersection of the sets given by two type expressions. • The unification of the two is the most general common instance of the two type expressions. K. Louden, Programming Languages
Unification need not result in a monotype. • (a,[a]) and ([b],c) unify as • ([b], [[b]]) K. Louden, Programming Languages
Patterns in Haskell • Patterns can be used in function definitions • Typical patterns are 0 for zero, [] for the empty list, and (x:xs) for a nonempty list. • fact and squarelist :fact 0 = 1fact n = n * fact (n-1)squarelist [] = []squarelist (x:xs) = (square x) : squarelist xs • Of course, it would be better to use map:squarelist lis = map square lis K. Louden, Programming Languages
Patterns in Haskell (cont.) • Because of patterns, it is unusual in Haskell to use head, tail, and null. • The anonymous wildcard pattern (matches anything) is the underscore in Haskell. • Overlapping patterns are ok (see fact on previous slide). Non-exhaustive patterns can also be used, and do not generate a warning • Patterns are syntactic sugar for case expressions:fact n = case n of 0 -> 1 _ -> n * fact (n-1) K. Louden, Programming Languages
Haskell List Comprehensions • Generators and filters can be expressed together in Haskell in a quasi-list notation called a list comprehension: odds = [n | n <- [1..], mod n 2 /= 0]-- can also write [1,3..] • Multiple generators and filters can be combined in a single list comprehension:mystery = [n+m|n <-[1..], m <-[1..], mod n m /= 0] • List comprehensions allow many snappy programs to be written: (mod without quotes is prefix)sieve (h:t) = h:sieve [n|n <- t, mod n h /= 0]primes = sieve [2..] K. Louden, Programming Languages
Haskell Data Structures • So far we have only seen lists in Haskell, although we know that static typing does not allow lists to imitate structs or classes as in Scheme. • Haskell has built-in tuple types. which are Cartesian products (like structs but with no field names):intWithRoot:: Int -> (Int,Double)intWithRoot x = (x , sqrt (fromInt x)) • Use patterns to get the components out:rootOf x = let (_,r)=intWithRoot x in r • Tuple types are written the same way values are, K. Louden, Programming Languages
Haskell Data Structures (2) • Lists and tuples do not go far enough, since unions cannot be expressed. • User-defined types can be introduced using data definitions:data Direction = North|East|South|West • Haskell can have polymorphic types and constructors with more than one parameter data Either1 a b = Left1 a | Right1 b fun ::Either1 a b -> Bool fun (Left1 _) = True fun (Right1 _) = False fun (Left1 5) True :t Left1 "hi" Left1 "hi" :: Either1 [Char] b K. Louden, Programming Languages
Haskell Data Structures (3) • Note how the previous definition expresses a tagged union of two polymorphic types. • Binary search tree example (recursive type):data BST a = Nil | Node a (BST a) (BST a)simpleBST = Node "horse" Nil Nil -- value • Constructors can be used as patterns:tree_to_list Nil = []tree_to_list (Node val left right) = (tree_to_list left) ++ [val] ++ (tree_to_list right) • Type synonyms can also be defined:type IntDouble = (Int,Double) • Note: all type and constructor names must be uppercase. K. Louden, Programming Languages
member a -> [a] ->Bool Can check for equality, but this only works when a is a type for which equality is defined. How do we express that? We call the collection of types over which a function is defined to be the type class. For instance, the set of types over which == is defined is the equality class. K. Louden, Programming Languages
How do we define such a class • We say what is needed for a type a to be in a class. In this case, we need == defined over a. In other words, a ->a->Bool class Eq a where (==) :: a-> a-> Bool Members of a type class are called its instances. Functions from Int->Int are NOT of type Eq since there is no algorithm to decide if two functions have the same behavior K. Louden, Programming Languages
Overloading in Haskell • Many functions in Haskell are overloaded, in that they can take values from a (finite) number of different types. An easy example is the square function, defined by square x = x * x. • The type of square in Haskell is:square :: Num a => a -> a • This says basically that square is defined for any Num a type (such types all have a * function). • The type Num a => a is called a qualified type, and Num is called a type class. • Type classes are a bit like Java interfaces: they require that a certain function be defined, and each associated type must then implement it. K. Louden, Programming Languages
Overloading in Haskell (2) • Here is an example of how to define a Sizeable type class that provides a measure of the size of a piece of data:class Sizeable a where size:: a -> Int • Now any type that you want to implement Sizeable must be declared an instance of the Sizeable class: (i.e., belongs to that class):instance Sizeable [a] where size = length instance Sizeable (BST a) where size Nil = 0 size (Node d l r) = (size l)+(size r) + 1 K. Louden, Programming Languages
Overloading in Haskell (3) • Now any use of the size function automatically adds the Sizeable qualification to a type:trivial x = size x == 0 This function has type:Sizeable a => a -> Bool • Type classes usually require multiple functions:class Num a where (+), (-), (*) :: a -> a -> a negate :: a -> a abs :: a -> a ...etc. instance Num Int where (+) = primPlusInt (-) = primMinusInt negate = primNegInt ...etc. Built-in "hidden" definitions K. Louden, Programming Languages
Overloading in Haskell (4) • Type classes may need to "inherit" functions from other type classes (similar to interface inheritance in Java):class Eq a where (==), (/=) :: a -> a -> Bool x == y = not (x/=y) x /= y = not (x==y) class (Eq a) => Ord a where (<),(<=),(>=),(>) :: a -> a -> Bool max, min :: a -> a -> a instance Eq Int where … instance Ord Int where ... Note default definitions K. Louden, Programming Languages
Numeric Type Class Hierarchy is read “is an instance of” K. Louden, Programming Languages
Overloading in Haskell (5) • The Show class allows a data type to be displayed as a String (e.g. by the interpreter):class Show a where show :: a -> String • So many data types need to be made instances of Show and Eq that Haskell can do it automatically:data BST a = Nil | Node a (BST a) (BST a) deriving (Show,Eq) • Overloading presents challenges for Hindley-Milner type checking that result in some surprises: squarelist = map square gives squarelist the type [Integer] -> [Integer] (no overloading!) K. Louden, Programming Languages