* Thoughts on O'Labl O'Caml merge. @ 1999-10-14 23:59 John Prevost 1999-10-17 11:01 ` skaller 0 siblings, 1 reply; 5+ messages in thread From: John Prevost @ 1999-10-14 23:59 UTC (permalink / raw) To: caml-list I was recently thinking about row polymorphism, structs and sum types. It occurred to me that one could do row polymorphism with sum types as well as with structs. My model was something like this (thought it requires a strange kinding system) (Well, it was too long, so I moved it to the bottom.) In any case, the point I have is that I came to the realization that SML (while not allowing row polymorphism) has different (and smaller) restrictions on the labels of struct types: fun x { a : int, b : float } = a + round b fun y { a : float, b : int } = a + float b You can use the same labels in more than one way. O'Caml, having taken a different road, requires that struct types be defined ahead of time, and labels cannot overlap--just like the current sum types in both languages. I thought "Hmm, it might be interesting to set up a language where this property is true of both sum types and struct types, and then add row polymorphism to it." Then I saw the messages about merging O'Labl and O'Caml, and started looking at O'Labl again, and saw the row polymorphism on polymorphic variants. And that made me think: "So now O'Caml will have a system which allows reusable sum labels, and non-reusable struct labels. And SML will have a system which has reusable struct labels but not reusable sum labels." Important questions: So--the heart of my question: would it be possible to, while adding row polymorphism on sum types (or a variation on that theme) also add it on struct types (or a variation of that theme) outside of objects? The only other thing I can think of which has been a thorn in my side is the overreach of the value restriction on functional values--which hurts a lot when you're doing stuff with purely functional or otherwise safe combinators. I don't know if this is open for discussion, as it's been hashed over on the mailing list in the past, but I suppose if things are being changed around, there might be a possibility. Here's the type system I was working on: <type> ::= int | bool | { <label> : <type> } | [ <label> : <type> ] | <type> + type | <type> | <type> | ... other base types, type constructors, etc. (where that last pipe is a pipe symbol, not another option. Silly notation on my part. :) Actually, the + and | type operators need to have {} and [] types, respectively, so: |- bool : base |- int : base |- { <label> : <type> } : struct |- [ <label> : <type> ] : sum |- <type1> : struct |- <type2> : struct ------------------------------------------ |- <type1> + <type2> : struct |- <type1> : sum |- <type2> : sum ------------------------------------ |- <type1> | <type2> : sum Which is a rather overcomplicated kinding system, but oh, well. I was just playing. So, with this system and polymorphism, you could have: { a : int } + { b : float } + 'a represents "any value that has at least fields a : int and b : float", and [ a : int ] + [ b : float ] + 'a represents "any value which has at least sum possibilities a : int and b : float" Other people have worked this out in better and greater detail than I have, including the "at least" vs "no more than" issue for sum types. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Thoughts on O'Labl O'Caml merge. 1999-10-14 23:59 Thoughts on O'Labl O'Caml merge John Prevost @ 1999-10-17 11:01 ` skaller 1999-10-17 19:13 ` John Prevost 0 siblings, 1 reply; 5+ messages in thread From: skaller @ 1999-10-17 11:01 UTC (permalink / raw) To: John Prevost; +Cc: caml-list John Prevost wrote: > Actually, the + and | type operators need to have {} and [] types, > respectively, so: > > |- bool : base > |- int : base > |- { <label> : <type> } : struct > > |- [ <label> : <type> ] : sum > > |- <type1> : struct |- <type2> : struct > ------------------------------------------ > |- <type1> + <type2> : struct > > |- <type1> : sum |- <type2> : sum > ------------------------------------ > |- <type1> | <type2> : sum > > Other people have worked this out in better and greater detail than I > have, including the "at least" vs "no more than" issue for sum types. Excuse me if I think aloud? When a function requires a struct with certain labels, we can use a product with 'enough' labels of the right type PROVIDED the fields are immuable. HOWEVER, these rules break down in the presence of mutable fields. The rule then is the dual, and combined, we obtain a requirement for invariance. Consider a representation of complex numbers using labels real, imag, mod, arg in which the cartesian/polar coordinates are both given and synched. Functions requiring either cartesian or polar coordinates will accept these values, and return either cartesian, polar, or the dual representation. But, if the functions _modify_ the record, the argument must be exactly the right type: either exactly polar, cartesian, or the dual representation, will be required. No matter which is required, no other type is acceptable. This observation is the key theorem which utterly destroys object orientation as a paradigm: because objects are useless unless mutable, and subtyping only works with immutable values, Oo is devoid of polymorphism. To give the classic example: as values, a square is a rectangle. Once mutation is permitted, functions requiring rectangles or squares require precisely rectangle or square arguments, and the other type will never do. The rectangle method 'set_sides(a,b) cannot be applied to a square, [overspecification] and the square method 'set_diagonal(d)' cannot be applied to a rectangle [underspecification]. [Technically, writing is dual to reading, however most 'mutators' are to be considered as requiring both reading and writing, so that both co- and contra- variance conditions collapse into invariance conditions] ------------------ What does this mean for the type system? There is a key theorem, developed for C++ pointer chains, which proves which conversions are const correct. Here is the theorem (excuse the C++ terminology): Let X1 be T1 cv1,1 * ... cv1,N * where T1 is not a pointer type, and let X2 be T1 cv2,1 * ... cv2,N * and where cvi,j is either 'const' or omitted, then a conversion from X1 to X2 is const correct iff 1) for all j, if cv1,j is 'const', then so is cv2,j 2) if there exists j, cv1,j <> cv2,k then for all k < j, cv2,j is 'const' To paraphrase 'it is not enough, to just throw const into the signature, if you throw one in, it must propagate to the top'. It is a somewhat surprising result, that adding in an extra const can destroy an otherwise safe conversion. Here is an example: T *** -> T const ** const* This conversion isn't safe. The reason is that the second pointer in the chain is mutable, and it points to a type which looks like T const **, so we can assign to it a T const *. However, the actual pointer is of type T*** which would allow writing into the 'const' field of the record we just assigned. The rule can be considered as requiring top level invariance, down to a certain location in the chain, where a normal 'subtyping' conversion is allowed (viewing a mutable value as a subtype of an immutable one). This rule must be applied in ocaml if open types are provided for records with mutable fields, since in this case, I imagine, the 'type' of a field in a signature can _also_ be a signature. Please read: C++: X const * as ocaml: { pointer: X } C++: X * as ocaml: { mutable pointer: X } where X can itself be a signature [or record type]. [I have modified the C++ theorem to elide reference to 'volatile'] What I _think_ this all means for ocaml, is that the rule for matching records with signatures must be extended to handle mutable fields, as described per field by the above theorem, on a field by field basis. -- John Skaller, mailto:skaller@maxtal.com.au 1/10 Toxteth Rd Glebe NSW 2037 Australia homepage: http://www.maxtal.com.au/~skaller downloads: http://www.triode.net.au/~skaller ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Thoughts on O'Labl O'Caml merge. 1999-10-17 11:01 ` skaller @ 1999-10-17 19:13 ` John Prevost 1999-10-19 19:19 ` skaller 0 siblings, 1 reply; 5+ messages in thread From: John Prevost @ 1999-10-17 19:13 UTC (permalink / raw) To: skaller; +Cc: caml-list skaller <skaller@maxtal.com.au> writes: > When a function requires a struct with certain labels, > we can use a product with 'enough' labels of the right type > PROVIDED the fields are immuable. > > HOWEVER, these rules break down in the presence > of mutable fields. The rule then is the dual, ... covariant, contravariant, invariant ... Ahh. Yeah, I'd forgotten this since I'm usually thinking in terms of immutable values. This gives, I think, greater understanding of why O'Caml's object system is made more powerful by not allowing object fields to be accessed outside the object they're part of. (As opposed to more traditional languages, which allow it within the entire class.) Guess that row-polymorphic structs aren't such a good idea, then. :) An aside about object design and O'Caml: Something I noted when explaining the O'Caml object system to some friends was a couple of places where choice of interface can be rather more subtle than, say, in Java. Specifically in the point/colored_point, circle/colored_circle examples from the manual (which I changed a little in my examples). class point = object val mutable x = 0 method get_x = x method move d = x <- x + d end type color = | Red | Green | Blue class colored_point = object val mutable c = Red method get_color = c method set_color nc = c <- nc end class ['a] circle (center : 'a) = object constraint 'a = #point val mutable center = center method get_center = center method set_center x = center <- x method move = center #move end This was my first quick pass (you can tell I wasn't referring to the manual too much.) To my chagrin, even without the colored_circle class, this doesn't perform the way you want it to: # let p = new point # let cp = new colored_point;; > val p : point = <obj> > val cp : colored_point = <obj> # let c = new circle p # let cc = new circle cp;; > val c : point circle = <obj> > val cc : colored_point circle = <obj> # (cc :> point circle);; > Characters 1-3: > This expression cannot be coerced to type > point circle = > < get_center : point; move : int -> unit; set_center : point -> unit >; > it has type > colored_point circle = > < get_center : colored_point; move : int -> unit; > set_center : colored_point -> unit > > ... removed Without even thinking, I chose a poor interface for my circle class. In fact, the type of colored_point is unfortunate, too. But since I can't have subtypes of sum types in O'Caml, it's not really a problem. If I were to use a class for this, or an O'Labl polymorphic variant, then it becomes an issue. The real problem in both of these cases is that in a powerfully typed OO world like O'Caml, it's dangerous to treat a class as a container when it isn't really. First, it constrains the type overmuch, making types you'd think to be subtypes not. Second, a major part of what this constraint is doing in the above case is revealing the implementation details of the class. I repeat the poor definition of 'a circle with notes and its class type: # class ['a] circle (center : 'a) = # object # constraint 'a = #point # val mutable center = center # method get_center = center # method set_center x = center <- x # method move = center #move # end;; > class ['a] circle : (* invariant 'a *) > 'a -> > object > constraint 'a = #point > val mutable center : 'a > method get_center : 'a (* covariant 'a *) > method set_center : 'a -> unit (* contravariant 'a *) > method move : int -> unit > end Okay, so the obvious symptom of the disease is that 'a appears covariantly in get_center, and contravariantly in set_center. But what's the root cause of these symptoms? Quite simply, I was clever and decided that if there were a get_blah method, and blah is mutable, there ought to be a set_blah method. Of course, I was wrong. Or was I? The other aspect of this definition is that it reveals the type of the center. This is useful in one respect--we can use it to define colored_circle: # class ['a] colored_circle (p : 'a) = # object # inherit ['a] circle p # method get_color = p #get_color # method set_color = p #set_color # end But here's a question: why do we want to expose the fact that the implementation uses a colored_circle to contain its center?? This may be a perfectly fine representation, but it's not really reasonable to expose it in this way. The model used by the documentation to get around this (though there was no discussion of what was going on) was to delegate to the point rather than allow it to be set. But getting the center was still allowed. I don't think it should have been. Here's are reasonable class types for circle and colored_circle, assuming that the rest of things (like how color works) are reasonable: class type circle : object method set_center : point -> unit method get_center : point method move : int -> unit end class type colored_circle : object inherit circle method set_color : color -> unit method get_color : color end Now. Why are these types appropriate? Don't they restrict the implementations to use only points? Not in the least! But what they do do is hide the implementation from the user of the above interface. The new semantics is that set_center means "move to the same position as the passed-in point" and get_center means "return a point representing the position of the center of the circle". Of course, one must also define whether or not changes in these points will affect the circle. Notice that colored_circle does not demand colored_points here. We can now write: class colored_circle2 = object val mutable p = new point val mutable c = Red method set_center p' = let cx = p #get_x in let dx = p' #get_x - cx in p #move dx (* Whoops. Point has no "set" method. *) method get_center = Oo.clone p method set_color c' = c <- c' method get_color = c end This shows how something can satisfy the colored_circle interface more easily now. Finally, what we really want to say about the circle class type is (in the latest O'Labl notation): class type circle : object method set_center : 'a . #point as 'a -> unit method get_center : point method move : int -> unit end This definition shows that anything implementing the same interface as point may be used. I'm still not entirely sure about this feature of O'Labl. Having the ability to make polymorphic methods is nice, but the greater need to specify types of values in O'Labl is rather upsetting. So, I think the result of this experience is as follows: * Treating classes as containers when they're really only delegating work to another class is dangerous--you expose the nature of the class you're delegating to, and make it difficult for subtypes of your class to delegate to different types of objects. * Sometimes it is appropriate to use a specific type instead of an object-scope type variable. Especially when you're setting up an API. This allows you to further hide the implementation details from API users. * Finally, method-scope polymorphism makes life simpler since you can pass subtypes more freely, as you can do with function polymorphism. (i.e. no more "circ #set_center (cp :> point)", or at least not as much.) John. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Thoughts on O'Labl O'Caml merge. 1999-10-17 19:13 ` John Prevost @ 1999-10-19 19:19 ` skaller 1999-10-20 14:33 ` Semantics for objects [Was: Thoughts on O'Labl O'Caml merge.] Francisco Valverde Albacete 0 siblings, 1 reply; 5+ messages in thread From: skaller @ 1999-10-19 19:19 UTC (permalink / raw) To: John Prevost; +Cc: caml-list John Prevost wrote: > Guess that row-polymorphic structs aren't such a good idea, then. :) I don't follow your reasoning: the rules are more complex than it might first seem just considering immutable fields. But the correct rules are known, and easy enough to implement. The fact that these rules restrict the correctness of use of mutable fields, is a consequence of correct typing, not an obstacle to implementing the correct rules. :-) > Something I noted when explaining the O'Caml object system to some > friends was a couple of places where choice of interface can be rather > more subtle than, say, in Java. The ocaml object system has the advantage of type correctness, which means that the limitations of object orientation are readily apparent. This is not so obvious in an 'object oriented' language, which is based from the start on a incoherent paradigm. > Specifically in the > point/colored_point, circle/colored_circle examples from the manual > The real problem in both of these cases is that in a powerfully typed > OO world like O'Caml, it's dangerous to treat a class as a container > when it isn't really. My opinion is quite different: object orientation cannot possibly work. It is completely unsupported by any coherent theory and can be so easily discredited by a single example that it is clear adherents were simply ignorant of basic theory. [no binary operator can be correctly represented; more generally, no n-ary relation for n>1] > Okay, so the obvious symptom of the disease is that 'a appears > covariantly in get_center, and contravariantly in set_center. But > what's the root cause of these symptoms? Simple. The covariance problem is a direct consequence of the incorrect assumption that a class can represent an abstraction. We know from category theory that a CATEGORY and NOT a class represents an abstraction, and an instance of the abstraction must be a functor. If you examine 'inheritance' you find it is not functorial. That is the problem. The paradigm is fatally flawed because it fails to obey simple rules of mathematics. An abstract function A --> B in a category must be instantiated as a function A' --> B', and that function is specific. There is no 'polymorphism' here. The polymorphism is entirely contained in the choice of instantiating functor. The different instances are 'unrelated' and cannot interoperate. Object orientation works in exactly one, well defined, case: when the methods of the class all have no arguments. [Technically, a function with an invariant argument is to be viewed as a family of functions with no arguments, for the purpose of this theorem] As I undertand it, in ocaml, a module is effectively a category, so we have the _correct_ model of abstraction in ocaml (even if it is restricted). Furthermore, type variables are simply a shorthand for using modules (with the advantage of implicit instantiation rather than the explicit instantiation required for modules) Please note: this does not mean that classes are useless! Contrarily, they're very convenient. What it means is that _abstraction_ is not representable this way, in general. Some cases are conveniently constructed using classes: since I have an OO background, I do this in my Python compiler. But I use ocaml signature matching .. not inheritance .. and the covariance problem is kept away only with some care, and because the use of the classes and interfaces is not really 'abstract'. [There are a finite set of classes with the signature, which is adjusted to accomdate all the required uses, sometimes with a little hackery thrown in] -- John Skaller, mailto:skaller@maxtal.com.au 1/10 Toxteth Rd Glebe NSW 2037 Australia homepage: http://www.maxtal.com.au/~skaller downloads: http://www.triode.net.au/~skaller ^ permalink raw reply [flat|nested] 5+ messages in thread
* Semantics for objects [Was: Thoughts on O'Labl O'Caml merge.] 1999-10-19 19:19 ` skaller @ 1999-10-20 14:33 ` Francisco Valverde Albacete 0 siblings, 0 replies; 5+ messages in thread From: Francisco Valverde Albacete @ 1999-10-20 14:33 UTC (permalink / raw) To: caml-list Il y a un petit resume en francais a la fin. ----------------------- skaller wrote: > My opinion is quite different: object orientation > cannot possibly work. It is completely unsupported by any > coherent theory and can be so easily discredited by a single > example that it is clear adherents were simply ignorant > of basic theory. [no binary operator can be correctly > represented; more generally, no n-ary relation for n>1] > > > Okay, so the obvious symptom of the disease is that 'a appears > > covariantly in get_center, and contravariantly in set_center. But > > what's the root cause of these symptoms? > > Simple. The covariance problem is a direct consequence of > the incorrect assumption that a class can represent an abstraction. > We know from category theory that a CATEGORY and NOT a class > represents an abstraction, and an instance of the abstraction > must be a functor. I think there's some people trying to give final coalgebra semantics to objects, certainly to "states". I have only began to understand the issue (I look at it from the perspective of labelled transition systems), but the landscape looks beautiful (if daunting!). Have a look at J.J.M.J. Rutten's page http://www.cwi.nl/~janr/ or specifically for objects B. Jacobs: http://www.cwi.nl/~bjacobs/ I think you'll find this interesting: B. Jacobs, Objects and classes, co-algebraically. In: B. Freitag, C.B. Jones, C. Lengauer, and H.-J. Schek (eds) Object-Orientation with Parallelism and Persistence Kluwer Acad. Publ., 1996, p. 83--103. Regards, Francisco Valverde -------------------------------------- Resume en (affreux) francais: Il semble qu'on peut assigner une semantique de coalgebre finale aux objects et ses classes. V. les URL cites en haut. ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~1999-10-21 9:43 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 1999-10-14 23:59 Thoughts on O'Labl O'Caml merge John Prevost 1999-10-17 11:01 ` skaller 1999-10-17 19:13 ` John Prevost 1999-10-19 19:19 ` skaller 1999-10-20 14:33 ` Semantics for objects [Was: Thoughts on O'Labl O'Caml merge.] Francisco Valverde Albacete
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox