Path: utzoo!attcan!utgpu!jarvis.csri.toronto.edu!mailrus!uwm.edu!gem.mps.ohio-state.edu!ginosko!brutus.cs.uiuc.edu!apple!agate!shelby!polya!carcoar!wilson From: wilson@carcoar.Stanford.EDU (Paul Wilson) Newsgroups: comp.lang.smalltalk Subject: types as roles [was Re: Smalltalk types] Keywords: types, first class types, protocols, type safety, static typing, roles Message-ID: <11746@polya.Stanford.EDU> Date: 12 Sep 89 18:59:09 GMT References: <11712@polya.Stanford.EDU> Sender: USENET News System Reply-To: wilson@carcoar.Stanford.EDU (Paul Wilson) Organization: Stanford University Lines: 131 I got several responses to the effect that my types-as-roles model is very intuitive, but pointing out what at first appears to be a problem with multiple inheritance. I don't think it's really a problem, and in fact is a major strength if you believe in strong typing. [ to recap a little: I said that types should be *roles* that objects can play, which is a lot like sets of messages they respond to. It's stronger than that, though, because it's an explicit declaration that the object understands the messages in a particular way. For example, what makes an object a number is not just that it understands messages like ">", but that it promises to respond to them in a way that makes sense as a number. This avoids accidental coincidences of naming, and in software engineering terms helps establish responsibility for whether something can be used in a particular context. I made the example of an object that is a window and is a queue as well, and can respond to the "size" message as either one; if you have a pointer to it as a window, it responds as a window, returning a point giving its screen dimensions. If you have a pointer to it as a queue, it returns an integer telling how many elements are in the queue. ] The problem comes up that you may have a window-queue object with a message name collision like this, and you may have a pointer to it as a window-queue, rather than as either a window or a queue. What happens then? I submit that you should have to explicitly disambiguate the context. This is not as gross and restrictive as it sounds, and in fact it's not restrictive at all. Remember, roles are *external* interfaces, and may cross-classify with inheritance for code reuse. If you have a pointer to something that can behave as a window or behave as a queue, it's no violation of information hiding to tell it what you mean. If you take the conversational message-passing metaphor seriously, this is exactly how things ought to be. If I've established more than one conversational context with somebody, ambiguities must be resolved. If I'm at a CS conference but I'm talking to somebody I've recently talked to about windsurfing, a question like "what do you do?" has to be disambiguated: "what do you do? -- as a hacker, I mean --" vs. "what do you do? Wavesailing, slalom?" This is not an invasion of my privacy because both contexts have already been established. Presenting myself as a CS guy and a boardhead does *not* imply, for example, that I'll answer questions about my religion or political views or whether my family is thoroughly inbred white trash: disambiguating *roles* doesn't imply knowing anything about the *class* of a reciever that it hasn't already told you. Oddly, I just came across something like this in a thoroughly *dynamically* typed language -- Ungar & Smith's "Self" language, which has no classes at all, just prototypes. In Self, multiple inheritance lookups are guided by the "sender path constraint," which sort of like the kind of disambiguation I'm talking about. It only works with messages an object sends to itself, though, because other objects can't tell anything about the type of the receiver. Basically, if an window-queue object sends itself a "size" message while executing a message inherited from "window," it responds with its window version. If it's executing a method inherited from "queue" when it sends itself a "size" message, it likewise assumes it's considering itself a queue at that point, and responds with the queue size. Actually, though, Self isn't making a distinction here between interface and implementation -- it assumes that they correspond. In a virtuous strongly-typed language (with all the Truth, Beauty, and Righteousness that does or doesn't imply), the roles would be explicit and checked. Since the size method of a queue is probably not an implementation of the same interface as the one for a window, no conflict would occur. If it *is* the programmer should know about it and have to say what he/she means. In general, I see Ungar's point of view that strong typing is a hassle and a pain in the ass. But I'm not sure you couldn't get rid of most of the hassle by the use of defaults and a little automation. For example, when you implement a subclass, it would be, by default, a subtype of the type(s) of its parent(s). And if you write code that assigns a particular value to an untyped variable, the type is inferred, etc. In general, pretty safe guesses would be made and the programmer's attention brought to the ambiguities. The latter might be deferred until runtime errors, or a tightening-up phase after a prototyping phase. You might have an optionally-typed language with automated support for going back in and strongly-typing things as needed. (Either to satisfy your anal-retentive supervisor :-) or if you feel that maybe your program's doing the wrong thing because you have undetected role mismatches.) It seems to me that programmers generally have a very good idea of what roles are appropriate at what point in a piece of code, and the trouble is not in any serious brainwork or overcommitment that the programmer must do, but in some low-level explication that might be largely automated, getting the best of both worlds. (Note that the programmer could always use the degenerate role "object" for things that must truly be dynamically typed. But for things in which the intent of the code obviously restricts the plausible roles anyway, like generic numeric code, specifying the role "number" is not restrictive. And if in some cases the programmer overcommits, then there should be automated support for generalizing, like going back and substituting a more general role and pointing out any mismatches or ambiguities it causes. This might actully be *more* flexible than dynamic typing, because some of the "freedom" of dynamic typing creates more work by making it harder to find hidden assumptions and dependencies. If you overcommited when you wrote the code, you might as well have done it explicitly, so a tool can help you fix it.) BTW, David Keppel has pointed out to me that Eiffel has a renaming facility, but it's really not doing the kind of disambiguation that I mean. It's for disambiguating name collisions *across* contexts, to establish a single non-role-dependent interface. This seems wrong and against the object-oriented grain to me. If an object is a queue and a window, it should be able to function both as a queue and as a window, without breaking existing code. The window management software should not have to be changed, and neither should software that uses queues. It should not matter to either one what some object does in other contexts, just as it is irrelevant that a word in English means something else in French. The introduction of bilingual entities should not require changes to either language, unless there's some fortuitous advantage to it. Paul R. Wilson Software Systems Laboratory lab ph.: (312) 996-9216 U. of Illin. at C. EECS Dept. (M/C 154) wilson@carcoar.stanford.edu Box 4348 Chicago,IL 60680