Version française
Home     About     Download     Resources     Contact us    
Browse thread
OO programming
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Jacques Garrigue <garrigue@m...>
Subject: Re: [Caml-list] OO programming
From: Tiphaine Turpin <Tiphaine.Turpin@irisa.fr>

> After a few unsuccessfull tries with using the object oriented features 
> of ocaml, I have been looking for ways to write classes that have a 
> chance to typecheck. The usual problem is that objects refer to other 
> objects, those objects may refer back to objects referring to them, then 
> objects refer to different objects, some of them having more methods 
> than others (subtyping), etc. and finally the programmer has to give a 
> type to all those beautifull mutable fields that are not fully 
> specified, or make them parametric. Of course, the resulting classes 
> should be reusable, that is, one should be able to extend a collection 
> of related classes simultaneously, such that the fields now have the 
> relevant subtype instead of the type they had before.

I must say first that I completely agree with Dirk Thierbach,
the easy way to do that is to connect actions rather than objects.
This is the signal/slot approach to GUI programming, and in my opinion
this works much better because you don't have to bother about
interface incompatibilities: you just do the plumbing by hand, without
needing strange wrapper classes.

> The best guidelines that I found are in the following course from Didier 
> Remy :
> 
> http://caml.inria.fr/pub/docs/u3-ocaml/ocaml-objects.html#toc13
> 
> He uses parametric classes  (parametric in the type of the related 
> objects) so that they can be extended. However I'm still unsatisfied 
> with this solution, because the related classes are written 
> independently, or, more precisely, their dependencies remain in the head 
> of the programmer and have no concretization in the language. For 
> example if a class refer to a method provided by a related class, this 
> way of writing them does not guarantee that the method is actually 
> defined. Only when creating and linking the objects together will the 
> type-checker reject the program, if for example, the method has been 
> misspelled in one class. So for me this coding scheme has the drawback 
> that it unplugs the type-checker and just call it at the end. For me the 
> "ideal" use of objects would use mutually recursive classes with fields 
> defined with a type referring to the name of the other class. The 
> problem is that simply using the closed type of the other classs often 
> prevent any further refinement.

No, the type checker is not unplugged. Internal coherence is still
verified for each class, this just the coherence of their combination
that has to wait until you put them together. I don't see why it's
wrong: if you want to check the compatibilty, just write a few lines
combining the objects, and you will catch all the errors you want, at
compile time.
But I think your disagreement lies at a  different level: OO
programmers often want to express their own view of things directly
into the language. So the problem is not about safety, or even
efficiency of debugging, but about writing down everything you want to
say where you want to write it. Reading the following paper made
clearer a feeling I already had before:

  Klaus Ostermann. Nominal and structural subtyping in component-based
  programming. Journal of Object Technology, 7(1):121 - 145, 2008.

There, he explains that he wants the ability both to declare that a
class implements an interface (when the class is defined), or that an
interface is implemented by a class (when the interface is
defined). He then goes on saying that structural subtyping in ocaml
provides neither. But this is false. The point in ocaml is that
you don't need to declare anything. But if you want to be sure, you
can check the subtyping relation:

  let _check x = (x : subtype :> supertype)

You can write this anywhere. This is a check, not a declaration.
If there are type parameters, you need to write a bit more:

  module Check : sig val f : 'a subtype -> 'a supertype end =
    struct let f x = (x : _ subtype :> _ supertype) end

This is because type annotations in ocaml do not enforce that variables
are kept polymorphic. I you want to check it, you have to repeat
yourself at the module level.

> Hence my question: does anyone knows a way of combining the reusability 
> of sets of related classes with a more modular (type/consistency)-checking ?

I'm not sure whether it helps, but I attach here the same example of
observer pattern as in the tutorial, but without using type
parameters. They are replaced by private row types.

Ideally, one would like to check coherence too by writing
  module rec Check : S' = Window(Check)
Unfortunately, this doesn't seem to work currently. I'm not yet sure
whether this is a bug or a limitation of recursive modules.

---------------------------------------------------------------------------
Jacques Garrigue      Nagoya University     garrigue at math.nagoya-u.ac.jp
		   <A HREF=http://www.math.nagoya-u.ac.jp/~garrigue/>JG</A>

module type S = sig
  type event
  type subject
  type observer = private <notify: subject -> event -> unit; ..>
end

module Any(X:S) = struct
  class virtual observer =
    object
      method virtual notify : X.subject ->  X.event -> unit
    end
  class virtual subject =
    object (self)
      val mutable observers : X.observer list = []
      method add_observer obs = observers <- (obs :: observers)
      method notify_observers (e : X.event) = 
        List.iter (fun x -> x#notify self#self e) observers
    end
end

module type S' = sig
  type event = private [> `Move]
  type subject = private <draw: unit; ..>
  type observer = private <notify: subject -> event -> unit; ..>
end

module Window(X:S') = struct
  module AX = Any(X)
  class observer =
    object
      inherit AX.observer
      method notify s e = s#draw
    end
  let count = ref 0
  class virtual subject =
    let id = count := succ !count; !count in
    object (self)
      inherit AX.subject
      val mutable position = 0
      method identity = id
      method move x = position <- position + x; self#notify_observers `Move
      method draw = Printf.printf "{Position = %d}\n"  position;
    end
end

module WindowA = struct
  type event = [`Move]
  class type observer =
    object method notify : subject -> event -> unit end
  and subject =
    object
      method add_observer : observer -> unit
      method draw : unit
      method identity : int
      method move : int -> unit
      method notify_observers : event -> unit
    end
end

module WindowF : sig
  class observer : WindowA.observer
  class subject : WindowA.subject
end = struct
  module WF = Window(WindowA)
  class observer = WF.observer
  class subject = object (self)
    inherit WF.subject
    method private self = (self :> WindowA.subject)
  end
end

let window = new WindowF.subject;;
let window_observer = new WindowF.observer;;
window#add_observer window_observer;;
window#move 1;;

module WRichT = struct
  type event = [`Raise | `Resize | `Move]
  class type ['subject, 'event] observer =
    object method notify : 'subject -> 'event -> unit end
  and ['observer,'event] subject =
    object
      method add_observer : 'observer -> unit
      method draw : unit
      method identity : int
      method move : int -> unit
      method notify_observers : 'event -> unit
      method raise : unit
      method resize : int -> unit
    end
end

module type S'' = sig
  type event = private [> WRichT.event]
  type observer = private (subject, event) #WRichT.observer
  and subject = private (observer, event) #WRichT.subject
end

module WRich (X : S'') = struct
  module WRF = Window(X)
  class observer =
    object 
      inherit WRF.observer as super
      method notify s e = if e <> `Raise then s#raise; super#notify s e
    end
  let string_of_event = function
      `Raise -> "Raise" | `Resize -> "Resize" | `Move -> "Move"
    | _ -> "Unknown"
  class trace = 
    object 
      inherit WRF.observer
      method notify s e =
        Printf.printf
          "<Window %d <== %s>\n" s#identity (string_of_event e)
    end
  class virtual subject =
    object (self)
      inherit WRF.subject
      val mutable size = 1
      method resize x = size <- size + x; self#notify_observers `Resize
      val mutable top = false
      method raise = top <- true; self#notify_observers `Raise
      method draw =
        Printf.printf "{Position = %d; Size = %d}\n"  position size;
    end
end

module WRichA = struct
  type event = [`Raise | `Resize | `Move]
  class type observer = [subject, event] WRichT.observer
  and subject = [observer, event] WRichT.subject
end
module WRichF : sig
  class observer : WRichA.observer
  class trace : WRichA.observer
  class subject : WRichA.subject
end = struct
  module WRF = WRich(WRichA)
  class observer = WRF.observer
  let string_of_event = WRF.string_of_event
  class trace = WRF.trace
  class subject = object (self)
    inherit WRF.subject
    method private self = (self :> WRichA.subject)
  end
end

let window = new WRichF.subject ;;
window#add_observer (new WRichF.observer);;
window#add_observer (new WRichF.trace);;
window#move 1; window#resize 2;;