Inheritance in O'Caml

Started by
6 comments, last by ToohrVyk 15 years, 4 months ago
I am finally buckling down and learning O'Caml. I know SML/NJ, so I am not lost in the functional aspect of the language. Where I am lost is the OO part. ocaml-tutorial.org says that unlike Java's method of inheritance, O'Caml prefers 'hooks'. Can someone clarify what this means, and how they differ? I'm not picking up on any subtle differences with their examples. Thanks!
Advertisement
/me looks at ToohrVyk...
What OCaml-tutorial says is this:

Quote:I've noticed programmers in Java tend to overuse inheritance, possibly because it's the only reasonable way of extending code in that language. A much better and more general way to extend code is usually to use hooks (cf. Apache's module API). Nevertheless in certain narrow areas inheritance can be useful, and the most important of these is in writing GUI widget libraries.


I understand this as "Inheritance is used a lot in Java because that's the Java way of doing things, but OCaml tends to do many of these things without inheritance. Nevertheless, here's how OCaml does inheritance:"

As a whole, OCaml inheritance has nothing to do with hooks: it works pretty much like Java inheritance (with a few notable exceptions, such as implicit interface implementation, and the possibility of multiple inheritance). Hooking, on the other hand, is just a technique for extending software that is similar to inheritance: instead of extending an entire class and all its methods, the behavior of individual methods is extended instead, at run-time instead of compile-time. It is a limited but lightweight combination of the Chain of Responsibility and Decorator design patterns.
As always, a tip of my hat to you good sir. I was having a bit of trouble understanding the distinction, when the examples seemed rather stereotypical OO to me.

Thanks again.
Not clear on the hooking thing. Is it something like this (Python)?

class Base:  def func(self): passdef Derived(): # "constructor" of non-existent Derived class  def _derived_func_impl(self):    print self  result = Base()  # replace base version of function with "override"  result.func = _derived_func_impl  return result
Well, hooking isn't replacement: it's addition. So, to implement a hook correctly, you have to add some call to base somewhere in there. The problem is that, since OCaml doesn't have dynamic inheritance models, you can't have inheritance-based hooking.

On the other hand, you can implement hooking in a quite simple fashion: keep a list of existing hooks and, when calling the hook stack, provide the function to call the rest to the top element as a first argument.

let rec call_hook_stack original stack data =   match stack with     | [] -> original     | h::t -> h (call_hook_stack original t) data


Consider what happens if (as in the Apache example) I'm trying to resolve an URL:

let http_serve_file path = file_contents pqthlet eliminate_private hooks path =   hooks (if is_prefix "/private" path then "/404.html" else path)let redirect_images hooks path =   hooks (if is_suffix ".jpg" path then "/images"^path else path)let stack = [ eliminate_private ; redirect_images ]let resolve_path path = call_hook_stack http_serve_file stack path


I could add new behavior to the resolving system simply by pushing a new element on top of the stack.
I am not sure if this example should be regarded as using hooks, but it is possible to create a record type consisting only of partially applied stub functions. The 'with' keyword in a record constructor could then be used to manually achieve overiding of default behaviour (eg of base_entity ).

type entity =    {    noise : unit -> unit ;    location : unit -> int;    };;let base_entity () =    {   noise = ( fun () -> ());        location = ( fun () -> 0 ); } ;;let make_player () =    let b = base_entity () in    { b with noise = ( fun () -> print_endline "burp" )  } ;;let make_dog is_hungry () =    let b = base_entity () in    { b with noise = ( fun () -> if is_hungry        then print_endline "grrrh (smiling)"        else print_endline "woof" )  } ;;(* let binding *)let e = make_player ();;e.noise();; let e  = make_dog true () ;;e.noise();; (* or generic traversal of single entity type *)   let entities = [ make_player (); make_dog true () ; ];;List.iter ( fun x -> x.noise() ) entities ;;


Edit: ok use source flags to avoid loosing indentation
Quote:Original post by chairthrower
I am not sure if this example should be regarded as using hooks, but it is possible to create a record type consisting only of partially applied stub functions. The 'with' keyword in a record constructor could then be used to manually achieve overiding of default behaviour (eg of base_entity ).


With hooks, the default behavior does something that's necessary, and all the hooks merely alter the parameters or return value of that default behavior—so, if the default behavior isn't called by your hook, then it's not really a hook.

Note that this approach of replacing behavior with the "with" keyword is equally possible, and even easier, using classes:

class base_entity = object  method noise = ()  method location = 0endclass player = object  inherit base_entity   method noise = print_endline "burp"endclass dog hungry = object  val hungry = hungry  method noise = print_endline    (if hungry then "grrrh" else "woof")endlet player = (new player :> base_entity);;player # noise;;let dog = (new dog :> base_entity);;dog # noise;;List.iter (fun entity -> entity # noise) [ player ; dog ]

This topic is closed to new replies.

Advertisement