10/5/09 ¡ cs242 � Parametric polymorphism � Single algorithm may be given many types � Type variable may be replaced by any type � if f::t → t then f::Int → Int , f::Bool → Bool , ... � Overloading � A single symbol may refer to more than one Kathleen Fisher � algorithm � Each algorithm may have different type � Reading: “A history of Haskell: Being lazy with class”, � Choice of algorithm determined by type context � Section 3 ( 3.8), Section 6 (skip 6.4 and 6.7) � Types of symbol may be arbitrarily different � � “How to Make Ad Hoc Polymorphism less ad hoc”, � � Sections 1 – 7 � + has types int*int → int , real*real → real , � “Real World Haskell”, Chapter 6: Using Typeclasses � but no others � Thanks to Simon Peyton Jones for some of these slides. Many useful functions are not parametric. � Many useful functions are not parametric. � Can member work for any type? � Can serialize work for any type? � member :: [w] -> w -> Bool serialize:: w -> String No! Only for types w for that support equality. � No! Only for types w that support serialization. � Can sort work for any type? � Can sumOfSquares work for any type? � sort :: [w] -> [w] sumOfSquares:: [w] -> w No! Only for types w that support ordering. � No! Only for types that support numeric operations. � First Approach � Second Approach � Allow functions containing overloaded symbols to Basic operations such as + and * can be overloaded, but not functions defined in terms of them. � define multiple functions: � 3 * 3 -- legal square x = x * x -- legal 3.14 * 3.14 -- legal -- Defines two versions: -- Int -> Int and Float -> Float square x = x * x -- Int -> Int square 3 -- legal But consider: � square 3.14 -- illegal squares (x,y,z) = Standard ML uses this approach. � (square x, square y, square z) Not satisfactory: Why should the language be able to -- There are 8 possible versions! define overloaded operations, but not the programmer? � This approach has not been widely used because of exponential growth in number of versions. � 1 ¡
10/5/09 ¡ First Approach � Second Approach � Equality defined only for types that admit equality : types not containing function or abstract types. � Make equality fully polymorphic. � 3 * 3 == 9 -- legal (==) :: a -> a -> Bool ‘a’ == ‘b’ -- legal \x->x == \y->y+1 -- illegal Type of member function: � Overload equality like arithmetic ops + and * in SML. � member :: [a] -> a -> Bool But then we can’ t define functions using ‘==‘: � Miranda used this approach. � member [] y = False Equality applied to a function yields a runtime error. � member (x:xs) y = (x==y) || member xs y Equality applied to an abstract type compares the member [1,2,3] 3 -- illegal underlying representation, which violates abstraction member “Haskell” ‘k’ -- illegal principles. � Approach adopted in first version of SML. � Only provides overloading for ==. � Third Approach � Make equality polymorphic in a limited way: � Type classes solve these problems. They � (==) :: a (==) -> a (==) -> Bool Allow users to define functions using overloaded where a (==) is a type variable ranging only over types operations, eg, square , squares , and member . � that admit equality. � Generalize ML ’ s eqtypes to arbitrary types. � Now we can type the member function: � Provide concise types to describe overloaded member :: [a (==) ] -> a (==) -> Bool functions, so no exponential blow-up. � member [2,3] 4 :: Bool Allow users to declare new collections of member [‘a’, ‘b’, ‘c’] ‘c’ :: Bool overloaded functions: equality and arithmetic member [\x->x, \x->x + 2] (\y->y *2) -- type error operators are not privileged. � Approach used in SML today, where the type a (==) is called an “eqtype variable” and is written ``a . � Fit within type inference framework. � Consider the “overloaded” function parabola : � Sorting functions often take a comparison parabola x = (x * x) + x operator as an argument: � We can rewrite the function to take the overloaded operators as arguments: � qsort:: (a -> a -> Bool) -> [a] -> [a] qsort cmp [] = [] parabola’ (plus, times) x = plus (times x x) x qsort cmp (x:xs) = qsort cmp (filter (cmp x) xs) ++ [x] ++ The extra parameter is a “dictionary” that provides qsort cmp (filter (not.cmp x) xs) implementations for the overloaded ops. � which allows the function to be parametric. � We have to rewrite our call sites to pass appropriate implementations for plus and times: � We can use the same idea with other overloaded operations. � y = parabola’(int_plus,int_times) 10 z = parabola’(float_plus, float_times) 3.14 2 ¡
10/5/09 ¡ Type class Type class instance declarations will declarations generate Dictionary generate instances type and accessor of the Dictionary functions. � data type. � -- Dictionary type -- Dictionary type data MathDict a = MkMathDict (a->a->a) (a->a->a) data MathDict a = MkMathDict (a->a->a) (a->a->a) -- Accessor functions -- Dictionary construction get_plus :: MathDict a -> (a->a->a) intDict = MkMathDict intPlus intTimes get_plus (MkMathDict p t) = p floatDict = MkMathDict floatPlus floatTimes get_times :: MathDict a -> (a->a->a) -- Passing dictionaries get_times (MkMathDict p t) = t y = parabola intDict 10 z = parabola floatDict 3.14 -- “Dictionary-passing style” parabola :: MathDict a -> a -> a If a function has a qualified parabola dict x = let plus = get_plus dict type, the compiler will add a times = get_times dict dictionary parameter and rewrite in plus (times x x) x the body as necessary. � “for all types w that support the Eq operations” � Type class declarations � Define a set of operations & give the set a name. � member:: ∀ w. Eq w => w -> [w] -> Bool The operations == and \= , each with type a -> a -> Bool , form the Eq a type class. � If a function works for every type with particular properties, the type of the function says just that: � Type class instance declarations � sort :: Ord a => [a] -> [a] Specify the implementations for a particular type. � serialise :: Show a => a -> String For Int, == is defined to be integer equality. � square :: Num n => n -> n squares ::(Num t, Num t1, Num t2) => Qualified types � (t, t1, t2) -> (t, t1, t2) Concisely express the operations required on Otherwise, it must work for any type whatsoever � otherwise polymorphic type. � reverse :: [a] -> [a] member:: Eq w => w -> [w] -> Bool filter :: (a -> Bool) -> [a] -> [a] FORGET all Works for any type ‘n’ you know that supports the Num about OO operations � classes! � ...the compiler generates this � When you write this... � square :: Num n => n -> n square :: Num n -> n -> n square x = x*x square :: Num n => n -> n The cl class de declaratio ion square d x = (*) d x x square x = x*x says what the Num operations are � class Num a where (+) :: a -> a -> a The “ Num n => ” turns into an extra value e argumen ent (*) :: a -> a -> a to the function. It is a value of data type Num n. negate :: a -> a An in instan ance This extra argument is a di dictio ionary y providing ...etc... de declaratio ion for a implementations of the required operations. � type T says how the instance Num Int where Num operations are a + b = plusInt a b implemented on T’ s � a * b = mulInt a b negate a = negInt a A value of type (Num n) is a dictionary ...etc... plusInt :: Int -> Int -> Int of the Num operations for type n � mulInt :: Int -> Int -> Int etc, defined as primitives 3 ¡
Recommend
More recommend