From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.1.3 (2006-06-01) on yquem.inria.fr X-Spam-Level: X-Spam-Status: No, score=0.4 required=5.0 tests=AWL,DNS_FROM_RFC_ABUSE autolearn=disabled version=3.1.3 X-Original-To: caml-list@yquem.inria.fr Delivered-To: caml-list@yquem.inria.fr Received: from mail1-relais-roc.national.inria.fr (mail1-relais-roc.national.inria.fr [192.134.164.82]) by yquem.inria.fr (Postfix) with ESMTP id 98864BBCA for ; Tue, 26 Feb 2008 07:18:02 +0100 (CET) X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Ao8CACI+w0eCNhAB/2dsb2JhbACSKZog X-IronPort-AV: E=Sophos;i="4.25,405,1199660400"; d="scan'208";a="8579584" Received: from unknown (HELO kurims.kurims.kyoto-u.ac.jp) ([130.54.16.1]) by mail1-smtp-roc.national.inria.fr with ESMTP; 26 Feb 2008 07:18:00 +0100 Received: from localhost (orion [130.54.16.5]) by kurims.kurims.kyoto-u.ac.jp (8.13.8/8.13.8) with ESMTP id m1Q6HtKd005866; Tue, 26 Feb 2008 15:17:55 +0900 (JST) Date: Tue, 26 Feb 2008 15:17:50 +0900 (JST) Message-Id: <20080226.151750.16504093.garrigue@math.nagoya-u.ac.jp> To: Tiphaine.Turpin@irisa.fr Cc: caml-list@yquem.inria.fr Subject: Re: [Caml-list] OO programming From: Jacques Garrigue In-Reply-To: <47BD44FE.3050001@irisa.fr> References: <47BD44FE.3050001@irisa.fr> X-Mailer: Mew version 4.2 on Emacs 22.1 / Mule 5.0 (SAKAKI) Mime-Version: 1.0 Content-Type: Text/Plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Spam: no; 0.00; irisa:01 ocaml:01 typecheck:01 subtyping:01 mutable:01 parametric:01 subtype:01 didier:01 parametric:01 dependencies:01 type-checker:01 type-checker:01 recursive:01 checker:01 coherence:01 From: Tiphaine Turpin > 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 JG module type S = sig type event type subject type observer = private 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 type observer = private 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 "\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;;