* [Caml-list] Narrowing class's public interface @ 2004-10-12 22:16 Tony Edgin 2004-10-13 0:49 ` Jacques Garrigue 0 siblings, 1 reply; 6+ messages in thread From: Tony Edgin @ 2004-10-12 22:16 UTC (permalink / raw) To: caml-list What is the reasoning behind Ocaml not allowing the public methods of a class to be hidden? Some times its nice for a class to have "global" public methods (Java's public methods) as well as public methods local to its module (Java's unprefixed methods). This allows for progressive encapsulation, where more distance modules no very little about a class, while the more local modules know more about the class. Inheritance could be used to approximate this, but sometimes subtyping makes more sense. I apologize for my lack of clarity. For those who couldn't understand my question, here's an example. module RandomVariables = struct class type rv_type = object method instance : float method scale_by : float -> rv_type method add : rv_type -> rv_type end class gaussian _stdev _mean = object method stdev = _stdev method mean = _mean method instance = (* elided *) (** Returns a draw from the corresponding distribution. *) method scale_by a = new gaussian ((abs_float a)*._stdev) (a*._mean) method add (other : guassian) = let sigma = sqrt (_stdev**2. +. other#stdev**2.) and mu = _mean +. other#mean in new gaussian sigma mu end (* Truncated *) end (* Bad code which I would like to use *) class gaussian_rv = (RandomVariables.gaussian float -> float -> RandomVariables.rv_type) ;; cheers. -- Tony Edgin CARP ------------------- 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] 6+ messages in thread
* Re: [Caml-list] Narrowing class's public interface 2004-10-12 22:16 [Caml-list] Narrowing class's public interface Tony Edgin @ 2004-10-13 0:49 ` Jacques Garrigue 2004-10-13 1:43 ` John Prevost 0 siblings, 1 reply; 6+ messages in thread From: Jacques Garrigue @ 2004-10-13 0:49 UTC (permalink / raw) To: edgin; +Cc: caml-list From: Tony Edgin <edgin@slingshot.co.nz> > What is the reasoning behind Ocaml not allowing the public methods > of a class to be hidden? This is just a type soundness problem: as an ocaml object type only describes the interface of an object, you have no way to make sure that an object had originally a specific method (with the right type) once you have hidden it. Of course we could add some kind of declaration like method hidden m : type and allow a subclass to convert a public method into such an "hidden" one, but I'm quite sure this is not what you are asking for? (Note that such an addition would be rather heavy, as "hidden" method must appear in object types too, i.e. < hidden m : type; ... >) > Some times its nice for a class to have "global" public methods > (Java's public methods) as well as public methods local to its > module (Java's unprefixed methods). This allows for progressive > encapsulation, where more distance modules no very little about a > class, while the more local modules know more about the class. > Inheritance could be used to approximate this, but sometimes > subtyping makes more sense. This problem has been thought of :-) Look at the user's manual, section 3.17, for how to define "friend" methods, i.e. methods accessible only from code in the same module. Note that since ocaml modules are hierarchical, this actually allows you more precision than Java's packages. http://wwwfun.kurims.kyoto-u.ac.jp/soft/ocaml/htmlman/manual005.html#ss:friends Jacques Garrigue ------------------- 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] 6+ messages in thread
* Re: [Caml-list] Narrowing class's public interface 2004-10-13 0:49 ` Jacques Garrigue @ 2004-10-13 1:43 ` John Prevost 2004-10-13 2:27 ` skaller 0 siblings, 1 reply; 6+ messages in thread From: John Prevost @ 2004-10-13 1:43 UTC (permalink / raw) To: caml-list To expand a bit on what Jacques said, there are a lot of places in O'Caml where there are very very elegant ways to do things that a background in more traditional OO languages might not make you think of. Here's a version of your original code that highlights just a couple of those things: module RandomVariables = (struct (* First thought: What if we make a generic random_variable class that could work for types other than float? The most generic version can only describe its type and the method that returns an instance of the RV. Here we see a class type with a type variable, describing a method instance that returns a value of that type. There's no real reason to define this class type, except to provide a shorthand--when we say "x random_variable", we mean a random variable that returns values of type x. *) class type ['a] random_variable = object method instance : 'a end (* Of course, the rv_type class that you originally described had slightly more functionality. Specifically, a scale_by method and an add method. The first thing to note here is the type 's, which is the "self type" for the object. An object that is a float_random_variable has a method scale_by that takes a float and returns a new object with the same type as the original object. Likewise, add on an object of type t takes a second object of the same type, and returns a new object of the type those objects share. This also shows us that if we want to add arbitrary random variables together (not just random variables of the same type), we're going to have to do things differently some day. The "inherit [float] random_variable" is shorthand for including that class type in this class type, specialized for the given type "float". Since O'Caml uses structural subtyping, it's not necessary to inherit just to say that this is a subtype. But it's easier than duplicating the definition. *) class type float_random_variable = object ('s) inherit [float] random_variable method scale_by : float -> 's method add : 's -> 's end (* This is the trick shown in the O'Caml manual for "friends". We define a new type gaussian_repr, which will carry the "secret value" for our gaussian random variables. We can't use the module type to hide the existence of a method for getting the internal representation--but we *can* prevent anyone outside this module from making use of the value once they get it. See below in the module's signature. *) type gaussian_repr = { g_stdev : float; g_mean : float } (* The class is refactored slightly. Note the use of {< ... >} instead of "new gaussian ...". This ensures that the new object is of the same type as the original object. If you ever want to inherit from gaussian to re-use code, this means that the inheriting class will work correctly, instead of returning gaussians it will return new instances of itself. Notice that we had to make a field repr to hold the internal state, instead of using the constructor's closure. Without the "val repr", we can't use {< ... >} to copy the object and change the intenral state of the new copy. The "method value" is another part of the friends trick. Notice how we use it in add to get the repr of the other gaussian. We could instead have hidden the return types of the original "stdev" and "mean" methods in your version--but I chose to change it to this so that it doesn't look too odd. Remember we can't get rid of the methods, so users of the API would wonder "why can't I get the stdev and mean out of the gaussian?!?" *) class gaussian stdev mean = object (_ : 's) val repr = { g_stdev = stdev; g_mean = mean } method value = repr method instance = (*fake*) repr.g_mean method scale_by a = {< repr = { g_stdev = (abs_float a) *. stdev; g_mean = a *. mean } >} method add (other : 's) = let repr' = other#value in let sigma = sqrt (repr.g_stdev ** 2.0 +. repr'.g_stdev ** 2.0) in let mu = repr.g_mean +. repr'.g_mean in {< repr = { g_stdev = sigma; g_mean = mu } >} end end : sig (* Okay. Signature time. The class types are identical. *) class type ['a] random_variable = object method instance : 'a end class type float_random_variable = object ('s) inherit [float] random_variable method scale_by : float -> 's method add : 's -> 's end (* And here's where we hide the type of the representation. We don't give a type equation after "type gaussian_repr", so the type becomes opaque outside of this module. *) type gaussian_repr (* And now the definition of the gaussian class's type. Here I just inherit all of the methods defined in the float_random_variable class type, and then add a new method "value", which returns type "gaussian_repr". The name might be even better "gaussian_internal_state" or whatever, to show why the type definition is hidden from outside. *) class gaussian : float -> float -> object inherit float_random_variable method value : gaussian_repr end end) And then we can proceed to use the above, which does compile cleanly. Now, something to note: Even though we inherit float_random_variable in the type for class gaussian, gaussian is not, and never will be, a subtype of float_random_variable. The trouble is that we have a binary method here. Because add has type 's -> 's, we can never discard any internal state access methods. Why not? Think about what happens if you put a variety of float_random_variables in a list. Now you take the first two items in the list and try to use #add on them. Say the first is a gaussian RV and the second is a log random variable. The definition of the method add can't deal with this--it will try to hand the log random variable's state to the internals of the gaussian RV, which has no idea how to deal with it. HOWEVER, if we throw away the binary methods, we can in fact put a collection of "float random_variable" objects in a list. Here's some output from an ocaml session: # let a = new RandomVariables.gaussian 0.0 1.0;; val a : RandomVariables.gaussian = <obj> # let b = (a :> RandomVariables.float_random_variable);; This expression cannot be coerced to type { lots of error stuff, which boils down to "can't get rid of that method" } # let c = (a :> float RandomVariables.random_variable);; val c : float RandomVariables.random_variable = <obj> As you can see, casting to float_random_variable didn't work, but casting to float random_variable worked just fine. The system is preventing us from doing something unsound: using a bunch of mixed float_random_variables together in a way that wouldn't work (because we can't add them together). But when we mix them in a way that will work (we'll only make instances of them), all is well. The kinds of things that are problemmatic in this system are far different from the kinds of things that are tricky in traditional OO systems. But with some experience, you can see how the system is amazingly powerful. I suggest reading not only the section of the manual on friends, but all of Chapter 3 (Objects in Caml) and Chapter 5 (Advanced examples with classes and modules). And feel free to ask about suggestions about how to structure your system. From the looks of your code, I have some ideas about where you might be going, and about designs that might work better in a few places. If you do ask, make sure to describe your problem in terms of what you want to do, rather than how you want to do it. (i.e. "I want to be able to add arbitrary random variables together", not "I want to be able to get run-time type information on my random variables so that I can find the code to add them together in the right way.") Good luck! 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] 6+ messages in thread
* Re: [Caml-list] Narrowing class's public interface 2004-10-13 1:43 ` John Prevost @ 2004-10-13 2:27 ` skaller 2004-10-13 6:39 ` John Prevost 0 siblings, 1 reply; 6+ messages in thread From: skaller @ 2004-10-13 2:27 UTC (permalink / raw) To: John Prevost; +Cc: caml-list On Wed, 2004-10-13 at 11:43, John Prevost wrote: > Even though we inherit float_random_variable in the type for class > gaussian, gaussian is not, and never will be, a subtype of > float_random_variable. The trouble is that we have a binary method > here. Indeed. More generally, objects require invariant (or contravariant) method arguments, but almost all 'real world' problems .. and mathematics ones in particular .. require covariant arguments, binary operators being a special case of that. Hence OO is useless as a general paradigm. It's a useful technique in special cases .. in particular you get dynamic dispatch. For example you can use OO abstraction for device drivers because the read and write methods only handle a *fixed* data type (characters). Don't even both trying to make the character type polymorphic because the theory says it can't be done. The reason is also easy to see, as John Provost described, but another view of the same thing: you'd need one method for every driver kind/character kind combination. In other words the number of methods needed is quadratic in the subtypes, but OO only supports linear -- you can supply a new method for each derived 'main object' type .. which is why the method arguments can't also be polymorphic. -- 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] 6+ messages in thread
* Re: [Caml-list] Narrowing class's public interface 2004-10-13 2:27 ` skaller @ 2004-10-13 6:39 ` John Prevost 2004-10-13 7:34 ` skaller 0 siblings, 1 reply; 6+ messages in thread From: John Prevost @ 2004-10-13 6:39 UTC (permalink / raw) To: caml-list On 13 Oct 2004 12:27:07 +1000, skaller <skaller@users.sourceforge.net> wrote: {...} > Hence OO is useless as a general paradigm. {...} {...} > Don't even both trying to make the character type polymorphic > because the theory says it can't be done. The reason is > also easy to see, as John Provost described, but another > view of the same thing: you'd need one method for every > driver kind/character kind combination. In other words > the number of methods needed is quadratic in the subtypes, > but OO only supports linear -- you can supply a new method > for each derived 'main object' type .. which is why the method > arguments can't also be polymorphic. Er. You're overstating the case here. First, note that OO programming and subtyping-based polymorphism don't have to go together. It's perfectly reasonable, for example, to have a container class that is invariant in the type of its content. And your IO example is flawed as well. The fixed data type is only an issue if you mix input and output in a single object. Example: exception Producer_Empty class type ['a] producer = object method get : unit -> 'a end class type ['a] consumer = object method put : 'a -> unit end class type ['a] producer_consumer = object inherit ['a] producer inherit ['a] consumer end class file_char : filename:string -> char producer_consumer class file_char_input_stream : filename:string -> char producer class char_to_line_stream : char producer -> string producer class map_producer : ('a -> 'b) -> 'a producer -> 'b producer val iter_over_producer : ('a -> unit) -> 'a producer -> unit class type bovine = object method moo : unit -> unit end class type cow = object inherit bovine method milk : unit -> milk end class type supercow = object inherit cow method fly : unit -> unit end val cows : cow producer_consumer The first thing to notice is that you can have a wide variety of I/O oriented classes (and functions over those classes, and classes over those classes) without worrying about the type of the item being produced or consumed. The second thing I want to point out is about "cows" above. cows cannot be used as a bovine producer_consumer or as a supercow producer_consumer. It can, however, be used as either a bovine producer, or a supercow consumer. Just because an object type is invariant does not mean that it cannot be cast to a covariant or contravariant type. In other words: you go too far to extend this argument to this level. When binary methods exist, things are indeed trickier. They are not, however, impossible. Again, remember that casting out the problemmatic methods can always restrict you down to a type with simpler constraints. In the RV example, you can think in terms of a complicated part of the program which manipulates and creates the RV objects, fully understanding that when manipulating those objects, the types must be maintained. But it can feed the objects it creates into a collection of restricted RV objects, which only carry the methods needed to use them, not the methods needed to manipulate them. No more binary methods, no problem having them all be the same type. I have to admit that I don't use the OO features of O'Caml very much--I generally don't need the flexibility. But these features are very very powerful, for a variety of uses. The trick is to get out of the *class* oriented mindset, and back into a mindset where the objects are what matters. After that, it's all a matter of learning what constraints type safety imposes, and the right way to work within those constraints. There are a few odd cases--like the constructor issue we saw on this list fairly recently, with the SQL connection pool--but if you understand the type system, it mostly all just makes sense. 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] 6+ messages in thread
* Re: [Caml-list] Narrowing class's public interface 2004-10-13 6:39 ` John Prevost @ 2004-10-13 7:34 ` skaller 0 siblings, 0 replies; 6+ messages in thread From: skaller @ 2004-10-13 7:34 UTC (permalink / raw) To: John Prevost; +Cc: caml-list On Wed, 2004-10-13 at 16:39, John Prevost wrote: > On 13 Oct 2004 12:27:07 +1000, skaller <skaller@users.sourceforge.net> wrote: > {...} > > Hence OO is useless as a general paradigm. {...} > Er. You're overstating the case here. I said OO is useless as general paradigm, I meant that and I don't think it is overstated. I also said objects have a limited use, where you don't need covariant methods. In such cases, you get dynamic (late) binding which is indeed very useful. ---> class type supercow = object inherit cow method fly : unit -> unit end <---- ... runs out to buy steel umbrella ... -- 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] 6+ messages in thread
end of thread, other threads:[~2004-10-13 7:35 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2004-10-12 22:16 [Caml-list] Narrowing class's public interface Tony Edgin 2004-10-13 0:49 ` Jacques Garrigue 2004-10-13 1:43 ` John Prevost 2004-10-13 2:27 ` skaller 2004-10-13 6:39 ` John Prevost 2004-10-13 7:34 ` skaller
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox