Show

We start with the simplest type class Show and its simplest instances. The standard Prelude class Show is a bit more complex and optimized; the main idea is the same. We always write type signatures, even though they are inferred, except for the very last example of polymorphic recursion.

Haskell OCaml class Show a where show :: a -> String instance Show Bool where show True = "True" show False = "False" instance Show Int where show x = Prelude.show x -- internal type 'a show = {show: 'a -> string} let show_bool : bool show = {show = function | true -> "True" | false -> "False"} let show_int : int show = {show = string_of_int} -- The first parametrically -- overloaded function print :: Show a => a -> IO () print x = putStrLn $ show x -- and its instantiation test_print :: IO () test_print = print True (* The first parametrically overloaded function *) let print : 'a show -> 'a -> unit = fun {show=show} x -> print_endline (show x) (* and its instantiation *) let test_print : unit = print show_bool true

The type-class declaration Show a in Haskell translates to the data type declaration for the record 'a show , a dictionary. The name of a type class method becomes the label in the dictionary. Let's compare the inferred types of the print function, in Haskell and OCaml. They are, respectively:

print :: Show a => a -> IO () print : 'a show -> 'a -> unit

The change in the shape of the arrow stands out: Show a => vs. 'a show -> . (There are other differences: the capitalization of some identifiers and the reverse meanings of the single and double colon.) In Haskell and OCaml, the function print is bounded polymorphic: it applies to values of any type, provided that we have the evidence that the type is in the class Show . In OCaml, it is the programmer who has to procure that Show -membership evidence, the dictionary, and explicitly pass to print . Haskell, in contrast, most of the time builds that evidence by itself and passes it implicitly.

The OCaml type of print reveals the nature of bounded polymorphism. An unbounded polymorphic function such as id : 'a -> 'a corresponds to the universally quantified proposition forall a. a -> a . The function print witnesses the proposition a -> unit quantified only over a part of the domain of discourse. The predicate show decides the membership in that part: we assert a -> unit only when show(a) . The proposition thus reads forall a. show(a) -> (a -> unit) -- which is the type of print modulo stylistic differences.

Next is the (simplified) Num type class, whose methods have a different pattern of overloading: the method fromInt is overloaded on the result type, and the method (+) is binary.

Haskell OCaml class Num a where fromInt :: Int -> a (+) :: a -> a -> a sum :: Num a => [a] -> a sum ls = foldr (+) (fromInt 0) ls type 'a num = {fromInt: int -> 'a; add: 'a -> 'a -> 'a} let sum : 'a num -> 'a list -> 'a = fun {fromInt = fromInt; add = add} -> fun ls -> List.fold_right add ls (fromInt 0) -- sample instance instance Num Int where fromInt x = x (+) = (Prelude.+) (* sample instance *) let num_int : int num = {fromInt = (fun x -> x); add = Pervasives.(+)} -- Two constraints print_incr :: (Show a, Num a) => a -> IO () print_incr x = print $ x + fromInt 1 -- An instantiation of the above print_incr_int :: Int -> IO () print_incr_int x = print_incr x let print_incr : ('a show * 'a num) -> 'a -> unit = fun (show_dict, {fromInt=fromInt;add=(+)}) -> fun x -> print show_dict (x + fromInt 1) let print_incr_int : int -> unit = fun x -> print_incr (show_int,num_int) x

Num

num

Bool

Int

print_incr

(Show a,Num a) => ...

'a show

'a num

print_incr

print

show_dict

fromInt

(+)

print_incr

To instantiate a bound-polymorphic function in Haskell we merely have to use it in a specific type context or give a specific type, see Haskell's print_incr_int . The type checker will verify that the specific type, Int in our case, is the member of Show and Num . These constraints of print_incr become resolved and no longer appear in the type of print_incr_int . On the OCaml side, we don't just make the type variable 'a to be int and let the type checker verify the constraint satisfaction. It is the programmer who has to prove that the constraints are indeed satisfied: the programmer has to find and explicitly pass the dictionaries show_int and num_int , as the proof that int is indeed a member of Show and Num . The type class abstraction does such proofs for us, searching for dictionaries and combining them in the complete evidence to pass to a parametrically overloaded function.

The next common pattern is an instance with a constraint: a Show instance for all list types [a] where the element type a is also restricted to be a member of Show .

Haskell OCaml instance Show a => Show [a] where show xs = "[" ++ go True xs where go _ [] = "]" go first (h:t) = (if first then "" else ", ") ++ show h ++ go False t testls :: String testls = show [1::Int,2,3] let show_list : 'a show -> 'a list show = fun {show=show} -> {show = fun xs -> let rec go first = function | [] -> "]" | h::t -> (if first then "" else ", ") ^ show h ^ go false t in "[" ^ go true xs} let testls : string = (show_list show_int).show [1;2;3]

The instance Show a => Show [a] now translates to a function, which receives the 'a show dictionary, the evidence that 'a is a member of Show , and produces the evidence that 'a list is also a member. As before `=>' becomes `->' in the translation. The occurrence show h in the Haskell code is not a recursive reference to the list show being defined. Rather, it refers to the show at a different type, the type of list elements. The OCaml code makes this reference clear. The specialization, testls , again involves more work on the OCaml side: we have to build the proof that int list is showable, by finding the evidence that int is showable and passing it to show_list to obtain the desired proof, that is, the function for showing integer lists.

For the final examples we need a class of comparable types:

class Eq a where (==) :: a -> a -> Bool

Bool

Int

type 'a eq = {eq: 'a -> 'a -> bool}

Haskell OCaml class (Eq a, Num a) => Mul a where (*) :: a -> a -> a x * _ | x == fromInt 0 = fromInt 0 x * y | x == fromInt 1 = y x * y = y + (x + (fromInt (-1))) * y instance Mul Bool where -- default instance Mul Int where x * y = (Prelude.*) x y -- internal type 'a mul = {mul_super: 'a eq * 'a num; mul: 'a -> 'a -> 'a} let mul_default : 'a eq * 'a num -> 'a mul = fun (({eq=eq},{fromInt=fromInt;add=(+)}) as super) -> {mul_super = super; mul = let rec loop x y = match () with | () when eq x (fromInt 0) -> fromInt 0 | () when eq x (fromInt 1) -> y | () -> y + loop (x + (fromInt (-1))) y in loop} let mul_bool : bool mul = mul_default (eq_bool,num_bool) let mul_int : int mul = {mul_super=(eq_int,num_int); mul=Pervasives.( * )} -- dot-product. There is only one constraint dot :: Mul a => [a] -> [a] -> a dot xs ys = sum $ zipWith (*) xs ys test_dot :: Int test_dot = dot [1,2,3] [4,5,6] (* dot-product. There is only one constraint *) let dot : 'a mul -> 'a list -> 'a list -> 'a = fun {mul_super=(eq,num);mul=mul} -> fun xs ys -> sum num @@ List.map2 mul xs ys let test_dot : int = dot mul_int [1;2;3] [4;5;6]

The default code for the multiplication recursively refers to the multiplication being defined. This reference happens at the same type: the recursion is ordinary, not polymorphic. Again this is apparent in the translation. It may be startling that the constraint in a class declaration looks and feels different from the constraint in an instance declaration. The instance Show a => Show [a] translates to a function, which takes a dictionary for 'a show and returns the dictionary for 'a list show : the instance for 'a show is required. On the other hand, class (Eq a, Num a) => Mul a translates to a dictionary that includes the pair of dictionaries 'a eq and 'a num . The two are hence provided by the 'a mul dictionary. The dot -product function receives only 'a mul but does not only multiplication but also addition. One may feel that the constraint in the class declaration should have been written as class (Eq a, Num a) <= Mul a . In fact, such a syntax has been suggested. The different interpretation of constraints in instance and class declarations is known, but not well, and can be confusing.

The final example deals with polymorphic recursion. The type signature becomes mandatory.

Haskell OCaml print_nested :: Show a => Int -> a -> IO () print_nested 0 x = print x print_nested n x = print_nested (n-1) (replicate n x) test_nested = do n <- getLine print_nested (read n) (5::Int) let rec print_nested : 'a. 'a show -> int -> 'a -> unit = fun show_dict -> function | 0 -> fun x -> print show_dict x | n -> fun x -> print_nested (show_list show_dict) (n-1) (replicate n x) let test_nested = let n = read_int () in print_nested show_int n 5

[[...[Int]...]]

n

x

list

show_dict

show_list

The explicit construction, deconstruction and passing of dictionaries in the OCaml code is annoying. What makes type classes popular in Haskell is the hiding of all this plumbing. The convenience increases when two type classes are involved, e.g., (Show a, Num a) in print_incr . In Haskell (Show a, Num a) and (Num a, Show a) are the same constraints -- but the corresponding types in OCaml ('a show * 'a num) and ('a num * 'a show) are different. Actually, OCaml has extensible records, in which the order of fields does not matter. These records are more appropriate for modeling dictionaries.