RCR 168: Interface Checking
submitted by sodell on 2003-11-19 18:05:20
This is a legacy RCR. If this is your RCR, please resubmit it using the new format and process.
I propose an addition to, or library for, Ruby to allow a form of type checking that I am referring to as "interface checking."
Since everything in Ruby is an object, it's less important that an object be of a certain type than that it implements a certain interface. After all, we don't care what an object is, we just care that it can do what we need it to do.
An "interface" in my usage below is just a name, an :id tag, that is assigned to objects when they wish to declare "I implement this interface."
A class that wishes to declare that it implements an interface can do so in several ways. Some examples:
class MyClass
end
The above class implicity declares "I implement the MyClass interface." Pretty simple.
Now another:
class MyClass < BaseClass
end
The above class inherits from BaseClass, and so declares "I implement both the BaseClass and MyClass interface."
Here's another:
class MyClass
include BaseClass
end
Now MyClass mixes-in BaseClass, so it again declares "I implement both the BaseClass and MyClass interface."
Now, here's an unusual example:
class MyClass
interface BaseClass
end
The above class declares "I implement both the BaseClass and MyClass interface" but it DOES NOT inherit from BaseClass. In this case, "interface" is a new keyword used to make the declaration without adding any functionality to the class. The declaration is just that: a declaration. It does not require inheritance or mixing-in a module. It's merely a declaration.
Every interface an object implements (declares to) could be stored as an array of :id values, much like the ancestry is stored. Every object could have a new method #implements?(:id) so we could ask if an object implements a certain interface.
A simple form of parameter checking could also be introduced to Ruby, as (optional) syntactic sugar, based on these interface declarations. An example:
def mymethod(:interface_id param)
end
The above method requires that its only parameter "param" implement the interface identified by :interface_id. If the programmer attempted to pass an object which did not declare itself as implementing this interface, Ruby could throw an informative error for the developer to handle.
So, in summary, I propose:
1) Adding an interface chain, similar to the ancestors chain, to every object, which an object can add to in order to declare that it implements an interface.
2) Automatically adding to an object's inheritance chain: its own name, the name of all inherited classes and all mixed-in classes.
3) Adding an interface keyword so objects can make that declaration without having to inherit or actually implement the interface.
4) Adding (optional) syntactic sugar to method parameters to perform enforcement that objects passed to a method declare themselves as implementing a given interface.
5) Adding an #implements? method to all objects to perform dynamic queries on an object's interface chain.
I believe this would an appropriate form of type checking for Ruby which is highly flexible, and completely ignorable if you wish. I think it win over more developers to Ruby who are skeptical about having zero type checking facilities while preserving Ruby's dynamic typing nature.
I also think it would help people integrate into their projects, outside code from libraries and code generated by other developers on multi-developer projects by giving them more useful error messages when they pass the wrong objects to methods which are not, or poorly, documented.
Vote for this RCR
Add comments here
<strong>Ruby IS dynamic language</strong>. What you propose is to introduce static typing into Ruby which violates the whole idea of Ruby Way. Please, understand that class declaration has nothing to do with interfaces or with idea of types. Class is just a convenient place to place your method declarations. An object can be changed dramatically during its lifetime and the type of the object is determined only by methods it responds to in any particular period of time.
Read the RCR. It's not static typing. The Ruby Way is to give programmers a language where things happen as they would expect. This is a very flexible way to help developers who call into foreign code a way to understand what it expected of them. It *is* The Ruby Way.
I'll tell you what's NOT The Ruby Way. Posting anonymously with a zealous tone of voice calling an RCR "static typing" when it's not. Do the Ruby community a favor and go hang around with the Python folks.
Of course Ruby is a dynamic language. I see nothing in the proposal that contradicts that. It sounds a lot less like static typing than something like optional soft contracts, which seems a reasonably Ruby-like answer.
Adding the "interface Blah" idea makes things more confusing, because now you have a new promise that can be made and then broken
class Bogus true
Bogus.new.implements? Blah #=> true
But, it doesn't act like a String or a Blah (assuming "Blah" has some expected behavior).
If you really want "type checking" or "interface checking", you've got to actually check the interface. And the interface is defined by what the object responds to. Class, module, and "interface" are irrelevant.
Try looking, as Glenn V mentioned, at the way Objective C implements this. It might work and actually do a better job of what you're trying to accomplish than this RCR does.
I personally don't find this idea interesting, even if it's made to be correct. But, that's my opinion.
Worse than what?
Anyway, I would love to agree, but I didn't think the dynamic typing crowd was much interested in hardened typing like that. It seems like there are advocates for dynamic typing, who then wish for something really solid in terms of typing. It's incongruous; you are either a fan of dynamic typing, and all of its terrify possibilities, or you want a solid promise of safety. Which is it?
I like dynamic typing, so all I need is a simple mechanism like this. I am very confused about the opinions around here. Dynamic is good, but if we are to have a type checking system, it must be rigorous. Thoroughly confused, I am.
With all of your talk of "contracts", it's a bad idea to promise something that you don't deliver. I'm not saying that we should have a rigorous type checking system. But, if we have one that we *claim* to be a contract, then it should actually function as such. Your RCR doesn't provide this.
So, let me say it this way since you're thorougly confused:
If you implement a type checking mechanism that you claim to be contractual, it should actually be contractual. Objects "declaring" to implement an interface should actually implement that interface. Your RCR introduces a new kind of inconsistency that would further complicate the language with no benefit. respond_to? does the job better than what you're proposing. Probably some kind of simple loop over a list of required symbols would be sufficient. This is similar to what types.rb does.
Maybe your RCR should be changed to include types.rb in the distribution?
No, because I don't want it to be contractual.
You read the RCR, right? Okay. That's how I want it. Call it what you want. Accuse me of calling something that it is not. Whichever the case, the way I wrote it in the RCR is how I want it. No more, no less.
Confused? No. Irritated? Yes.
Look at Euphoria's type system. It allows programmers to define type requirements with validation code, and can be turned on or off. I think that system would serve your needs and desires better. in fact its *almost* already doable:
type biggerthen4(x)
unless x&gt; 4
raise
end
x
end
def somemethod(biggerthan4 x)
...
end
somemethod(3) # error
...what value does it really provide?
I'm looking at it and seeing no value-add. It doesn't ensure that I don't misuse the interface, and if I claim to implement an interface but don't actually do so, I have an additional level of indirection to find the real problem.
Yes, I read the RCR. I also read your email. In your email you said you wanted constructive criticism on your RCR. My constructive criticism was that I feel like this makes things worse, but if you really want a type checking system, you should check out Objective C and try to find some ideas there. I'm sorry that irritated you.
Now it's time to let the votes and matz decide.
In your example biggerthen4 does not define a type. It is just a reusable pre-condition checking for arguments of methods, but it does not define a new type. Pick up any book in your local book store about OOP and start being more constructive.
No it is not a Ruby-like answer. By declaring a contract, Ruby does not prevent you from violating this contract at any time simply by undef_method.
The only way to enforce a contract is by using respond_to?.
I looked over the Euphoria system and believe that it requires type signatures on all methods, which is (I think) what some people don't want -- including myself. It also seems to be "class name"-based, which is excessively restrictive, given Ruby's flexibility.
-austin
I said "soft contract", not strictly enforcable contract. That means the contract is cooperative only. When my method declares that it wants an argument that does :x, :y, and :z, then you have to supply it with an argument that "says" it does those things (ie, your argument is an object that declares those methods as part of its interface). Of course your object may be lying about its interface, or it may have the same interface but with incompatible semantics, and then all bets are off. No one is suggesting full bondage and discipline here. Even respond_to? is only such a cooperative contract anyway.
The value is that objects can declare that they will work with each other and a syntax sugar to make them easy to use can be added so it's relatively painless to use.
It seems like you are bucking for strong typing in Ruby. You keep saying "if it doesn't enforce, what's the use?" You want enforcement? You want strong typing in Ruby?
To reject a loose idea like this is just asking for something you know Matz will never be able to add to Ruby without completely changing the way Ruby works, or without some sort of massive runtime overhead to generate signatures on-the-fly constantly.
It's loose or it's strict, up to you all.
Personally I think there are some very good features of this idea. It's like the xml validation approaches: Schematron ("rule-based") vs W3C schemas ("declarative"). In the XML world, people see these two as being complimentary. Certainly neither is rubbished.
There are several positive points:
- there is a "type name" that can be referred to by programmers/documentation. "oh, that method takes an XYZ object"
- the "type name" of each parameter to a method can be queried at runtime
- all sorts of checking can be done in the "test", including kind_of or responds_to, but also extending to contractual obligations of the input argument. In fact it seems to be a complete "design-by-contract" implementation, ala Eiffel (not that I'm an expert in this area).
- it seems pretty trivial to disable the checking for production.
And it does seem very rubyish.
Does anyone have any *concrete* objections to this?
One concern I have is that it introduces a pretty severe performance hit if all this checking is done on each call. This is not the case with Sean O'Dell's original "interface checking" proposal. Is there some kind of optimisation that could be applied?
Another is that it is not possible to ask an object "what types are you compatible with". An object does not "declare its interfaces" as per Sean's proposal. I'm not sure that this is actually terribly useful in practice though.
-- Simon Kitching
PS: don't bother complaining that I am not using the word "type" correctly. I don't care; you all know what I (and the original poster) mean in this context.
The original author never suggested adopting Euphoria's system complete and unaltered.
I can't see anything in the proposal which means that every parameter *must* have a type signature. If there is one, and "checking" is enabled then check it. If there is no associated "type check" for a parameter then it doesn't get checked.
In fact, I think the word "contract" (rather than "type") would truly be appropriate for these checks.
Austin, you stated that you were interested in the design-by-contract ideas, yet rubbish this which is basically the same concept. Or have I missed some critical difference between the two?
And what on earth leads you to state that this proposal is "classname-based"?
If a "contract" is written in terms of kind_of, then yes. But if it is written in terms of "responds_to" then no. Perhaps the Euphoria examples are written in terms of kind_of, but that's a convention that doesn't need to follow into the Ruby world...
-- Simon Kitching
It's good that you've noticed how the concept of "interface," which is a explicit special type in Java (and implemented in C++), arises in Ruby. Ruby is, however, a dynamic language. Because of this, there doesn't need to be a static interface checker. You simply write your object to meet an interface, and then think of it of being "of that interface." If you don't implement all methods that will be called during the lifetime of your object, you will 'probably' get a runtime error. This isn't true in some cases, say, when there is a method that gets called only on special occasions. I believe people have been proposing perhaps adding runtime warnings to tell you that "this may not work because you didn't make these methods that could possibly be called." However, the dynamic nature of Ruby that gives rise to this behavior in a natural fashion, would make this only a warning, and not an error.
It may be nice to perhaps have a flag that will make the interpeter pre-scan the source and output these kinds of potential errors, but not bother you with them when you run regularly. Technically in Ruby there is no need to define an interface, or to even define something like templates. Since things are typeless, the desired functionality of templates exists by default, as does the desired functionality of an interface. However for the purpose of writing code that is robust, and likely not to fail intermittently at runtime, it may be nice to provide utilities for figuring out what problems may occur.<br />
Changes to Ruby should be carefully thought over, the last thing we want to do is turn it into a frankensteined language, like VB or C++.<br />
I believe, still, that I understand what problem you're trying to address with this proscription.<br />
Let's say you wrote some code:
class Bob
def initialize(myObj)<br />
if(Time.now.sec == 32)<br />
myObj.oneMethod<br />
else<br />
myObj.theOtherMethod<br />
end<br />
end<br />
end
class Joe
def initialize<br />
end<br />
def theOtherMethod<br />
puts('Ruby rules!');<br />
end<br />
end
myBob = Bob.new(Joe.new)
It's often useful to implement an 'interface' in this fashion, it allows your code to be generic. However, when programming this, it's highly likely you'll never see the program crash due to the fact the programmer didn't complete the 'interface.' But, say he, the generic programmer, finishes this code, it seems cherry, and he sends it out to operate a heating-cooling system for a factory project. Goes home, sleeps, and while he's sleeping KABOOM! The system explodes because the program crashed. Okay, this probably isn't a highly likely occurrence -- but, in a statically defined language, this type of problem would be caught and not allowed. It may be nice to have a utility to profile the code to see if errors like this occur. Making the programs static is obviously not an option, and neither is just saying "Don't use Ruby in this fashion." This is a HIGHLY desirable effect. And I personally deal a great deal with real-world instruments, and often overlook Ruby. Not because Ruby isn't more efficient, or not fast enough, but because it's not safe enough. I know what people are going to say, "It's your fault that it isn't safe enough, you're a trashy programmer, etc.."<br />
In response to this:
The fact of the matter is, my caliber as a programmer doesn't matter, what matters is that my project works in the end. It would be nice for Ruby to provide this type of crutch to those not as l337 hax0r as yourself. Certainly not in the way suggested here, but in another way definitely.
-Jason Thomas.
Uglyness, you'll just have to grit your teeth and deal with it. Sorry :(
Back to RCRchive.
RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog