From oleg-at-okmij.org Thu Mar 8 22:36:58 2007 To: caml-list@inria.fr Subject: Operator overloading From: oleg-at-pobox.com Message-ID: <20070309073658.8F020AD34@Adric.metnet.fnmoc.navy.mil> Date: Thu, 8 Mar 2007 23:36:58 -0800 (PST) Status: OR This message illustrates the translation of Haskell98 non-constructor classes to OCaml, which gives us bounded polymorphism and a sort of polymorphic recursion in OCaml. It has to be remarked first that overloading is quite a complex issue: please see `A Theory of Overloading' by Stuckey and Sulzmann (TOPLAS 2005) for many gory details. The issue of the type of the plus operation: 'a->'a->'a or 'a->'b->'c is not an abstract subtlety. That's why functional dependencies where introduced in Haskell (and quite often we need local functional dependencies to resolve overloading). There are three main techniques of implementing overloading: full inlining (aka C++), dictionary passing, and the intensional type analysis. The latter is like `switch' and dictionary passing is like a `vtable'. GHC and Hugs implement dictionary passing, whereas JHC and Chameleon implement typeclasses via intensional type analysis. I suspect F# might be doing something like that too, because CLI might have run-time type tags. Non-constructor Haskell 98 classes (that is, overloading over parameter of the kind *) can be implemented in OCaml in a straightforward way. We start with a typical Haskell code class Numb a where add :: a -> a -> a shw :: a -> String instance Numb Int where add x y = x + y shw = show instance Numb Float where add x y = x + y shw = show instance Numb a => Numb [a] where add = zipWith add shw a = "[" ++ concatMap (\x -> (shw x) ++ " ") a ++ "]" summ (h:t) = foldl add h t test1 = shw (add [(1::Int),2,3] [4,5,6]) test2 = shw (summ (add [(1::Int),2,3] [4,5,6])) test3 = shw (summ [(1.0::Float),2.0,3.0]) test4 = shw (summ [[(1.0::Float),2.0,3.0], [4,5,6], [7,8,9]]) We introduce a class Numb with two methods, for addition and for showing. The latter is quite handy. The instances for Int and Float are trivial. The instance of [a] says that if the type of list elements is in class Numb, the list is in class Numb as well. Note the definition for the "add" method in that case. It looks recursive: the body of 'add' invokes 'add' itself, but on a different type. We then define a function summ. Its inferred type is summ :: (Numb a) => [a] -> a The function is bounded polymorphic: it works on lists of any type provided that type is in Numb. The tests test3 and test4 demonstrate that summ can sum lists and lists of lists, etc. Here's the OCaml translation: (* class Numb a *) type 'a numb = {add: 'a -> 'a -> 'a; shw: 'a -> string};; (* instance Numb Int *) let numb_i = {add = (+); shw = string_of_int};; (* instance Numb Float *) let numb_f = {add = (+.); shw = string_of_float};; (* instance Numb a => Numb [a] *) let numb_l numb_e = {add = List.map2 numb_e.add; shw = fun a -> "[" ^ List.fold_right (fun e z -> " " ^ numb_e.shw e ^ z) a "]"};; (* we can define a bounded polymorphic function summ *) let summ numb (h::t) = List.fold_left numb.add h t;; let test1 = let n = numb_l numb_i in n.shw (n.add [1;2;3] [4;5;6]);; let test2 = let n = numb_l numb_i in numb_i.shw (summ numb_i (n.add [1;2;3] [4;5;6]));; let test3 = numb_f.shw (summ numb_f [1.0;2.0;3.0]);; let test4 = let n = numb_l numb_f in n.shw (summ n [[1.0;2.0;3.0]; [4.0;5.0;6.0]; [7.0;8.0;9.0]]);; The inferred type of summ in OCaml is val summ : 'a numb -> 'a list -> 'a = It is instructive to compare it to the Haskell type. The only difference is in the shape of the arrow: "Numb a =>" vs "'a numb ->". Instead of the double arrow of Haskell we have the single arrow in OCaml. The OCaml function summ is likewise bounded polymorphic: it applies to arrays of any type provided that we have the _evidence_ (the dictionary) that the type is in the class numb. We must pass that evidence as the first argument of summ. Granted, the burden of procuring this evidence is on us; Haskell, in contrast, can, most of the time, build that evidence by itself. As in Haskell, we can sum lists of numbers and lists of lists of numbers, etc. Haskell constructor classes (overloading over the parameters of higher kinds) do require OCaml functors. The example is a monad. With a bit of syntactic sugar, the monadic notation and the corresponding overloading are tolerable in OCaml. Conversely, OCaml and SML modules (including sealing, generative and applicative functors and recursive structures) can be emulated in Haskell typeclasses. Chung-chieh Shan and I wrote a couple of messages on that topic back in August and September 2004 on the Haskell mailing list. John Skaller wrote: ``(Haskell does this little cheat .. it makes typeclasses very easy to explain to OO people).'' Are you sure about that? There has been many long and recurring threads on Haskell-Cafe about typeclasses and OO classes. I think the common advice is _not_ to think of Haskell typeclasses as of OO classes. Ralf Laemmel and I once wanted to elaborate on the issue of OO in Haskell; we ended up with a 79-page paper. I guess that shows that OO is never easy.