* [Caml-list] Restricting Method Overriding/Redefinition in Subclass @ 2004-08-14 0:03 chris.danx 2004-08-14 7:38 ` skaller 0 siblings, 1 reply; 7+ messages in thread From: chris.danx @ 2004-08-14 0:03 UTC (permalink / raw) To: Caml Mailing List Hi, Methods provide access control in the form of private or public methods, but I am in a position where I would like the client to extend some methods but not others, and would like some advice if possible. class type virtual base_class = object (self) method add_child (c : base_class) -> unit method remove_child (c : base_class) -> unit method remove_all_child (c : base_class) -> unit method private children : unit -> base_class iterator method virtual perform_action unit -> unit end;; perform_action is supposed to do something with the rendering state and call each of it's childrens perform_action methods in turn (unless it's a leaf). base_class is the base class for all objects in a scene graph. I have narrowed the behaviour of perform_action down to three possibilities. Either it does something before calling it's children, it does something after calling it's children or it does something before and after calling it's children. This leads either to one class with additional two methods (recode base_class to include pre and post ops) or three classes with either a pre or post call or both. To ensure objects don't do extra work I was considering the three class solution or a set of classes paramterised by closures which resolve to essentially the same thing. e.g. class type prechild_class = object(self) inherit base_class method virtual pre_op unit -> unit method perform_action unit -> unit end;; or (* pre_op_func is a typically a closure and essentially behaves like a hook. *) class type prechild_class (pre_op_func : unit -> unit) = object(self) inherit base_class ... method perform_action unit -> unit end;; Basically pre_op can be overridden to perform the desired function, with perform_action calling it before iterating over the children or a closure can be provided and called by perform_action. Is it possible to prevent the redefinition of perform_action by a subclass? If not I can live with it, but if there is a way it would serve to enforce the intended behaviour of the class. Namely that it should do what it needs to do and call it's children somewhere in perform_action. One could rely on the subclass to encode this behaviour, but it would be very easy for programmers to forget this which leads to a violation of what could be regarded as a class invariant. Perhaps a radically different solution is best? I chose an OO solution so that new classes could be trivially added to the system. It just occured to me that the same functionality can be provided with records where all the operations are closures. Pass in the relevant functions to a function and get a closure with all the operations of the original class. It's entirely possible I'm over analysing the problem and should use something simpler as I've done that before on more than one occassion. Any advice is welcome. Cheers, Chris ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-14 0:03 [Caml-list] Restricting Method Overriding/Redefinition in Subclass chris.danx @ 2004-08-14 7:38 ` skaller 2004-08-14 16:28 ` John Prevost 0 siblings, 1 reply; 7+ messages in thread From: skaller @ 2004-08-14 7:38 UTC (permalink / raw) To: chris.danx; +Cc: Caml Mailing List On Sat, 2004-08-14 at 10:03, chris.danx wrote: > Perhaps a radically different solution is best? I chose an OO solution > so that new classes could be trivially added to the system. There is another kind of solution you might investigate. Instead of trying to unify all your data types using abstraction -- classes provide abstraction -- use summation instead. A summation solution looks like: type obj = [ | `A of a | `B of b * obj | `C of c * obj * obj ] using polymorphic variants. In order to 'do something' with each object, including children, you write a 'visitor' style function: let rec iter x = match x with | `A a -> do_a a | `B (b ,kid) -> do_b b; iter kid | `C (c, k1, k2) -> iter k1; do_c c; iter k2 When you need to add a new object type to this system you have to add the method for handling it, just as in the OO solution. The difference is that the method doesn't go in with the data lexically, but separately in the visitor function. Ocaml will make sure you don't forget. This kind of solution is more flexible than the OO style solution, since you can write many different kinds of 'visitor' style functions without invading your data descriptions. The downside is that the code for each kind is scattered through the program, whereas with classes its localised lexically. Which solution is best depends on how homogenous your object kinds are. If they're all instances of a single abstraction -- use classes. If they're heterogenous objects -- use summation. You can of course mix both solutions together -- abstract what really is abstract, and unify what is not using variants. -- John Skaller, mailto:skaller@users.sf.net voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-14 7:38 ` skaller @ 2004-08-14 16:28 ` John Prevost 2004-08-14 17:17 ` skaller 2004-08-18 12:04 ` chris.danx 0 siblings, 2 replies; 7+ messages in thread From: John Prevost @ 2004-08-14 16:28 UTC (permalink / raw) To: Caml Mailing List John Skaller's solution is probably a good choice here. To understand why restricting subclassing doesn't do much good in O'Caml, you have to understand how O'Caml's object system is different from that of most class-based languages you've worked with before. The key insight is in the area of subtyping. In C++, Java, and the like, subtyping and subclassing are combined with each other. You cannot have a value which is of type A unless that value's class is either A or a subclass of A. Here's an example from Java: class A { public int getX() { return 1; } } class B { public int getX() { return 2; } public int getY() { return 3; } } B is not a subclass of A (it is not declared as "extends A"), therefore an instance of class B cannot be used where an instance of class A is expected. In O'Caml, this is distinctly not the case: class a = object method get_x () = 1 end class b = object method get_x () = 2 method get_y () = 3 end let use_get_x o = o#get_x Here, the function "use_get_x" can be used on instances of any class that contains a method get_x : unit -> int. This is called "structural subtyping". The subtype relationship is based purely on what methods exist, not on what classes have been declared to be subclasses of others. So for your example, where you want to restrict the implementation of the subclasses, you can't really do it--any object that has all of the methods of class base_class can be coerced to type base_class and added with add_child. So even if you were to restrict how your subclasses have to work, you can't be sure that the values you're given are actually produced from those subclasses. (Well, not totally true: you can do it if you make your class type opaque--but this will prevent subclassing entirely, which you don't want.) At this point, you may be wondering "Why is O'Caml like this? Is it a good thing to separate subtyping and subclassing this way?" So I'll give a couple of examples of how this type discipline is less of a pain than the one used by Java. 1) Implementing multiple interfaces. In Java, things sometimes get awkward if you want to work with values that implement multiple interfaces at once: interface Colored { Color getColor(); } interface HasPoint { Point getPoint(); } class MyThingy implements Colored, HasPoint { ... } Okay, so the difficulty in this set of classes and interfaces (which *has* actually bitten me in the wild fairly frequently) is that there is no way to declare a variable as "any object that is of both type Colored and type HasPoint." If we do this: interface ColoredHasPoint extends Colored, HasPoint { ... } then MyThingy is not an instance of ColoredHasPoint. It has all of the appropriate methods, but it was not *declared* to have this type. Therefore, it does not have the correct type. So you end up having to write: void useColoredHasPoint(Object o) { Colored c = (Colored) o; HasPoint h = (HasPoint) o; ... = c.getColor(); ... = h.getPoint(); } and obviously the system has now broken down. AS long as you work with only one interface at a time, you're in fine shape. As soon as you need more than one and have no other requirements, you're in trouble. In O'Caml, of course, the above is trivial: let use_colored_has_point o = let color = o#get_color () in let point = o#get_point () in ... 2) Subclasses that are not subtypes This is a really interesting case, actually. The short explanation is that if you let subclassing not require subtyping, then you can change the types of the arguments of the subclass's methods. That raises the question "Why would you want to do that?" Say you have a class that implements linked lists: # class ['a] linked_list (x : 'a) = object (_ : 's) (* self type for the "next" pointer *) val mutable next = (None : 's option) method set_next n = next <- Some n method clear_next () = next <- None method next = next method value = x end;; class ['a] linked_list : 'a -> object ('b) val mutable next : 'b option method clear_next : unit -> unit method next : 'b option method set_next : 'b -> unit method value : 'a end Now, looking at this type, we see that there's a method "set_next" that takes the self type as an argument. As a result, *any* subclass of linked_list that adds methods will not in fact be a subtype. So why would you ever want to subclass this, anyway? Well, perhaps you want to *re-use code* when writing a doubly linked list. # class ['a] doubly_linked_list (x : 'a) = object (self : 's) inherit ['a] linked_list x as ll val mutable prev = (None : 's option) method set_next n = match next with | None -> ll#set_next n | Some n' when n = n' -> () | Some n' -> (ll#set_next n; n'#clear_next (); n#set_next self) method set_prev p = match prev with | None -> prev <- Some p | Some p' when p = p' -> () | Some p' -> (prev <- Some p; p'#clear_next (); p#set_next self) method clear_prev () = match prev with | None -> () | Some p -> (prev <- None; p#clear_next ()) method prev = prev end;; class ['a] doubly_linked_list : 'a -> object ('b) val mutable next : 'b option val mutable prev : 'b option method clear_next : unit -> unit method clear_prev : unit -> unit method next : 'b option method prev : 'b option method set_next : 'b -> unit method set_prev : 'b -> unit method value : 'a end And we see upon using them: # let w = new linked_list 10 let x = new linked_list 20 let y = new doubly_linked_list 30 let z = new doubly_linked_list 40;; val w : int linked_list = <obj> val x : int linked_list = <obj> val y : int doubly_linked_list = <obj> val z : int doubly_linked_list = <obj> # w#set_next x;; - : unit = () # y#set_next z;; - : unit = () # x#set_next y;; This expression has type int doubly_linked_list but is here used with type int linked_list Only the first object type has a method clear_prev # z#set_next w;; This expression has type int linked_list but is here used with type int doubly_linked_list Only the second object type has a method clear_prev We see upon using them that these types aren't compatible in O'Caml--not in either direction. A linked_list cannot be used as a doubly_linked_list, a doubly_linked_list cannot be used as a linked list. And it turns out that this stays true even if you try to coerve the types. Why? Because the doubly_linked_list only works if its "prev" and "next" pointers are also doubly linked lists--it needs to update its neighbours to make sure that x -next-> y implies that y -prev-> x. On the other hand: # let rec iter_list f l = match l#next with | None -> f l | Some n -> f l; iter_list f n;; val iter_list : ((< next : 'a option; .. > as 'a) -> 'b) -> 'a -> 'b = <fun> # iter_list (fun v -> Printf.printf "%d\n" v#value) w;; 10 20 - : unit = () # iter_list (fun v -> Printf.printf "%d\n" v#value) y;; 30 40 - : unit = () This works because both the linked_list and doubly_linked_list types are subtypes of a third object type: the one that only cares about the "next" and "value" methods, and doesn't touch the set_next method or anything else. So anyway, the whole point here is that "class x is a subclass of class y" does not entail "type x is a subtype of type y". And this is a good thing, because it enriches our world. Now subtyping means that two values can be used in the same contexts (iter_list works on both linked_list and doubly_linked_list, even though they've not been declared as subclasses of < next : 'a option >). And subclassing means that we're sharing code (linked_list and doubly_linked_list share code, even though their types are incompatible.) I hope my long-winded explanation was useful. John. ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-14 16:28 ` John Prevost @ 2004-08-14 17:17 ` skaller 2004-08-18 12:04 ` chris.danx 1 sibling, 0 replies; 7+ messages in thread From: skaller @ 2004-08-14 17:17 UTC (permalink / raw) To: John Prevost; +Cc: Caml Mailing List On Sun, 2004-08-15 at 02:28, John Prevost wrote: [structural subtyping] > At this point, you may be wondering "Why is O'Caml like this? My stupid answer is "because its correct". The kind of subtyping by subclassing used in Java and C++ is really awkward to use. Basically the problem is, to extend the capabilities of your object heirarchy, you inevitably have to add a new method to a base class, or equivalently derive from an extra base class containing it -- which breaks the principle of encapsulation that is supposedly the foundation of object orientation: such breakage is said to be invasive, and it contravenes the Open/Closed principle (see Meyer). This isn't the case for structural subtyping, where A is a subtype of B simply if it is one -- you don't have to derive A from B -- which might not even exist at the time you write A. This means you can write algorithms that work on sets of classes sharing some properties (such as a collection of methods) AFTER you define the classes without invading the class definitions -- in Java or C++ you'd have to add inheritance specifications to make this work. -- John Skaller, mailto:skaller@users.sf.net voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-14 16:28 ` John Prevost 2004-08-14 17:17 ` skaller @ 2004-08-18 12:04 ` chris.danx 2004-08-18 19:47 ` John Prevost 2004-08-18 22:21 ` skaller 1 sibling, 2 replies; 7+ messages in thread From: chris.danx @ 2004-08-18 12:04 UTC (permalink / raw) To: John Prevost; +Cc: Caml Mailing List John Prevost wrote: [snip] > let use_get_x o = o#get_x > > Here, the function "use_get_x" can be used on instances of any class > that contains a method get_x : unit -> int. This is called > "structural subtyping". The subtype relationship is based purely on > what methods exist, not on what classes have been declared to be > subclasses of others. I didn't realise OCaml supported this. I wanted to write code like that but didn't realise it was possible in OCaml. This opens the door to a new way of writing programs. Thanks! > At this point, you may be wondering "Why is O'Caml like this? Is it a > good thing to separate subtyping and subclassing this way?" So I'll > give a couple of examples of how this type discipline is less of a > pain than the one used by Java. It's not so strange to me. Separating subtyping from subclassing is something I'd read about (in the context of polymorphism and subclassing) and thought was very useful. It gives you the ability to write code that works over different classes of object, without specifying the precise type of the object (it's type is implied by the methods it has). This is why I didn't realise OCaml provides the ability to do this. I'd assumed that the typing rules of OCaml enforced the fact that the methods and classes had to exist before the inference of the type was done and that it'd be one of those classes. Thinking about it that doesn't make much sense. Now it's clear how the new immediate objects work. It was a bit puzzling before, as there seemed no way to use them. Immediate objects seem to make it trivial to write something like a proxy providing you know the methods before hand. A more interesting case would be if you didn't know the methods before hand but you could access them in someway and build objects at runtime and you could somehow guarantee the result was compatible. Not sure how that'd fit with OCamls typing scheme, but it's interesting to think about. > 1) Implementing multiple interfaces. ... > 2) Subclasses that are not subtypes ... The first one is easy to understand, but the second example took a while to get my head around. I think I understand the implications of it now, although it is still a bit unclear to me how the reuse works in this example. I sort of understand it, but bits of it are unclear. Need to think about it for a while. > I hope my long-winded explanation was useful. Very! OCaml is a big language - in the sense there's a lot of power there - and some of it is still hidden to me so I appreciated your explanation. Thanks, Chris ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-18 12:04 ` chris.danx @ 2004-08-18 19:47 ` John Prevost 2004-08-18 22:21 ` skaller 1 sibling, 0 replies; 7+ messages in thread From: John Prevost @ 2004-08-18 19:47 UTC (permalink / raw) To: chris.danx; +Cc: Caml Mailing List On Wed, 18 Aug 2004 13:04:44 +0100, chris.danx <chris.danx@ntlworld.com> wrote: > > 2) Subclasses that are not subtypes > > The first one is easy to understand, but the second example took a while > to get my head around. I think I understand the implications of it now, > although it is still a bit unclear to me how the reuse works in this > example. I sort of understand it, but bits of it are unclear. Need to > think about it for a while. Well, to be honest, the example is not a very good example. With the linked lists and doubly linked lists, the amount of code that is re-used is pretty small compared to the amount of work that needs to be done to re-use it. Try looking at the section on binary methods in the OCaml manual (Section 3.16) for a much much simpler example of a subclass that is not a subtype. Both section 3 and section 5 of the manual are very very good things to read to get a handle on this object system. John. ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] Restricting Method Overriding/Redefinition in Subclass 2004-08-18 12:04 ` chris.danx 2004-08-18 19:47 ` John Prevost @ 2004-08-18 22:21 ` skaller 1 sibling, 0 replies; 7+ messages in thread From: skaller @ 2004-08-18 22:21 UTC (permalink / raw) To: chris.danx; +Cc: John Prevost, Caml Mailing List On Wed, 2004-08-18 at 22:04, chris.danx wrote: > Separating subtyping from subclassing is > something I'd read about (in the context of polymorphism and > subclassing) and thought was very useful. You aren't going far enough. Subtyping is a *semantic* notion. Whether a class satisfies the 'is a' relation or not is the issue. In Ocaml, if a class has a type which is not an *Ocaml* subtype of another it probably isn't a subtype semantically *** The converse is not true. Just because Ocaml thinks your class has a subtype of another classes type does NOT mean it actually is a subtype -- you still need to check the methods to make sure the 'is a' property holds. If you override some method and give it an incompatible behaviour you don't have a subtype (you'll just get weird bugs!) *** It is possible to have a class which acts semantically 'as' another but is not ruled by Ocaml as a subtype. A trivial example of this is when you have two methods called 'size' in one class and 'length' in another that do the same job -- but happen to be encoded with distinct names. You'll have to use a wrapper to fix this without invading one of the classes (and then probably breaking something else). -- John Skaller, mailto:skaller@users.sf.net voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2004-08-18 22:21 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2004-08-14 0:03 [Caml-list] Restricting Method Overriding/Redefinition in Subclass chris.danx 2004-08-14 7:38 ` skaller 2004-08-14 16:28 ` John Prevost 2004-08-14 17:17 ` skaller 2004-08-18 12:04 ` chris.danx 2004-08-18 19:47 ` John Prevost 2004-08-18 22:21 ` skaller
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox