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: Tiphaine.Turpin <Tiphaine.Turpin@f...>
Subject: Re: [Caml-list] OO programming
Jacques Garrigue a écrit :
> 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.
>   
First, my desired use of objects is not (reduced to) programming a gui. 
I can imagine that, in guis, the actions that may be invoked on objects 
or that may originate from them are just signals with rather simple 
types, which form a simple case of object communication for which action 
connection is enough, but when it comes to using objects to model the 
objects of the application domain, stronger links (i.e., fields) are 
sometimes natural. Note that, in class diagrams, links between classes 
are one of the most basic concepts.

>   
>> 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.
Yes, of course. The word (unplugged was a provocation ;-)).

>  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.
>   
Yes, writing such a "challenge" just after the definition (and before 
continuing to extend things) looks less beautifull than we would like, 
but it should work (I didn't try it).

> 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.
Yes. Clearly, I want the abstractions, patterns, etc. I have in mind to 
be expressed themselves as language objects, and not just their 
"instanciation". Isn't this the very goal of high-level languages after 
all ?
>  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.
>
>   
An interesting reading. The expressive power that he adds to nominal 
subtyping gives me more arguments to consider structural subtyping not 
very usefull (but I understand that structural is much more consistent 
with everything else in ocaml).
> here, 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,
which tutorial ?

>  but without using type
> parameters. They are replaced by private row types.
>   
which is very verbose (having to explicitely write class types), and I'm 
not sure I understand the benefits of doing this.
> 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.
>   
If the typing of recursive modules just types the body of each module in 
the environment given by the declared module types of all modules 
involved in the recursive definition, then this cannot work, since 
Window(Check) would have to be of type S' for any Check : S', which is 
clearly false (e.g. Check = struct ... type  observer = <foo: unit; 
notify : ...).

I have a hard time understanding your code, and'm still unsure of the 
rationale. So, instead of recursive classes, you write a functor of 
which the classes are the fix point ? And the private row types are here 
just to allow you to add structural contraints on types in a module type 
? The functors have the advantage that they group the classes together 
in "something", and forcing the relation between their parameters in S'' 
is interesting. Too bad that your module rec doesn't work...

As for extension, I'm fully satisfied. But the verbosity level is 
annoying for scalability...

Tiphaine Turpin
> ---------------------------------------------------------------------------
> 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;;
>
> _______________________________________________
> Caml-list mailing list. Subscription management:
> http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
> Archives: http://caml.inria.fr
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>