From: Peter Date: 2003-11-21T10:51:16+09:00 Subject: Re: "stereotyping" (was: Re: Strong Typing (Re: Managing metadata about attribute types) ) Hi Sean, The ruby code below illustrates some of my views on type/interface checking. First of all I'm not that fond of interface checking using respond_to? for several reasons. The simplest - and least debateable one - being that it fails when any method_missing trick is used. Second, saying that any object responding_to? the method quack means the object can quack like a duck is stereotyping as well. If an object has this method, it doesn't guarantee it can quack like a duck. If an object doesn't have this method, it doesn't mean it can't quack like a duck. Of course while ruby does provide a method to abstract an actual type from having a certain name among the names of its ancestors, it doesn't abstract the actual type from having certain names among its implemented methods. This is just a thought that's been bugging me for a while now, mainly because of stuff like name clashes, and the "what's in a name" phrase (a quack sounds the same by any other name). Recently, as I was reading up on my Haskell knowledge, something struck me. In Haskell you can specify a number of types, and specify a number of functions that work with this type (although it is not an OO language, it allows for OO-like stuff, but not the usual syntax). The types can also be encapsulated. However there is no inheritance among types. Now this is where Haskell classes come in. They are much like interfaces, saying that some type in that class responds to some functions. Any type that wants to belong to a class needs to provide an implementation for each of the functions of that class - and here's the cool part - without a necessary correspondence between the name of the function in the class and the name of the function in the type. For example a type could implement a function "doSomeQuacking", a class could require a function "quack", and so one could specify that the function "quack" from that class should really call "doSomeQuacking" on the type. Also a type like CharlatanDuck below could belong to class Duck as well as class Quack, both having a function quack. There's no problem with ambiguity since asking a CharlatanDuck to quack in its role of Duck, it will quack like a duck, and in its role as Quack, it will quack like a quack. BTW, remark that Haskell's classes are truly classifications based on functionality, which is better naming than ruby's classes which refer to a code vehicle. Anyway, it's just a feature I'd personally like to see in ruby too if type checking makes it there, but I'd make it optional too. Note that although the type checking in Haskell is static, if you'd get your interface checking through, something likewise could be added for free (in terms of run time overhead) if you'd want, despite the fact that ruby is dynamically typed. It's just an idea that will also be shot down because of increases in number of lines, because of limited useability and because it's not needed (but just for the sake of argumentation, technically no one really needs ruby either, it's just more convenient for some people than other languages, or a matter of taste, principle, etc.) As for increase in number of lines for type checking in general; if it saves documentation because it's self-documenting - not to mention that the documentation is guaranteed to be consistent with the behavior - and if it saves some checks of your own - suppose the same set of checks, or a subset, etc. occur in several methods - and if it saves some testing, it could be a gain, right? But that needs to be proven still... Having used the occasion to put forth yet another stupid idea, I'll get to your proposal... Momentarily you can't work the ObjectProxy below with your technique, or anything that includes a method_missing technique. I have no idea how to merge that into your technique since it requires analyzing the body of the method_missing method and anything it calls, which for one thing is an undecideable problem, but also it needs to be redone as code changes. Maybe you should provide some way of delegating the interface check, e.g., to $obj in the example below, but then you'd give the programmer control and he or she can lie about what interfaces are implemented. Personally, I find that not so much a problem because the only effect is that it backfires on the one who lies. It *is* a problem if the idea of the interface is used when interfacing with other languages, but I guess I don't do that often. But since your interface check is just a check (and a cheap one :-) for the moment, I guess this is no issue (yet). Note that you can't simply have the ObjectProxy declare the interfaces it implements because we don't know what kind of object will be behind it - it's a matter of flexibility. Another thing; since methods can be removed and added, maybe it's interesting to also be able to add or remove the notification that you intend to implement an interface. As such, no dynamism is lost there. But it's an extra complication :-( Last thing (unless I come up with something else before I send this mail); you should probably also provide a way of declaring superinterfaces and subinterfaces. Suppose someone provides an interface InputOutput which provides methods for inputting and outputting (what else?). If some method only uses the input part of the interface, you want to declare a superinterface Input of InputOutput. I call it a superinterface since an InputOutput can pass for an Input, but not the other way around. You'd also want to declare subinterfaces, e.g., a StringInputOutput which offers some additional functionality to InputOutput. Either you'd need to make interfaces open, then you can just get by with declaring subinterfaces, or otherwise you need both subinterfaces and superinterfaces. (At least one programming language I know of can do both subclassing and superclassing, so it's perfectly possible). Just for all clarity, making a superinterface to an existing interface can happen when that existing interface is in a library and you can't modify it (because it's closed). Then instead of saying in the existing interface what the superinterface is, you'd say in the superinterface that the interface from the library is a subinterface. OK, head is empty now. BTW, I really admire you for still being here after all the heat :-) Peter PS: To everyone: I want to make this very clear; I'm not taking a stance here whether type checking should happen or not. But I do think it's an interesting experiment, whether you'd want it or not, and whether that is for legitimate or illegitimate reasons. It's already pretty clear who wants it and who does not. I think it's interesting, and only if we let Sean work this through, we'll find out about the true value. It's not as if we can change his mind anyway :-) Be like Matz and give him a chance! --------------------- class ObjectProxy def initialize(obj) $obj = obj end alias_method :method_missing_old, :method_missing def method_missing(s, *args) if $obj.respond_to?(s) print "start of call to #{s.id2name}\n" result = $obj.send(s, *args) print "end of call to #{s.id2name}\n" result else method_missing_old(s, args) end end end class Duck def quack print "quack quack!!!\n" end end class DuckTapedDuck def doSomeQuacking print "quack quack!!!\n" end end class Quack def quack print "quack potion!!! quack pills!!! all very cheap, works better than the real stuff!\n" end end class CharlatanDuck def quack print "want me to quack like a duck, or quack like a quack?\n" end end p Duck.new.respond_to?(:quack) Duck.new.quack # a duck can quack like a duck p Quack.new.respond_to?(:quack) Quack.new.quack # a quack can quack like a quack, but can't quack as a duck p CharlatanDuck.new.respond_to?(:quack) CharlatanDuck.new.quack # ambiguous request p DuckTapedDuck.new.respond_to?(:quack) DuckTapedDuck.new.doSomeQuacking # and still I can quack like a duck p ObjectProxy.new(Duck.new).respond_to?(:quack) ObjectProxy.new(Duck.new).quack # and still I can quack like a duck p ObjectProxy.new(Duck.new).respond_to?(:doSomeQuacking) ObjectProxy.new(Duck.new).doSomeQuacking # fails, this time rightly so