Taking comments into consideration, a totally new approach strikes me
regarding type checking.
As briefly as I can:
What if we had a way to describe a class interface that basically does
this: “from this point forward, any methods added to this class
definition will be checked against an interface description, and any
methods whose names matches a method described in the interface must
take the number and type of parameters described, and the class will
be considered incomplete until the very last method described by the
interface is added to the class.”
*** Some basic rules about this:
Classes do not need an interface. They can go on just the way the
are now, completely open and free. For someone who doesn’t like to
use interfaces, they are completely unaffected.
Interfaces are immutable and cannot be extended, but you can subclass
other interfaces from them.
Why? Is there any reason that interfaces shouldn’t be as mutable as
classes and/or modules are?
I’ve played with a soft interface implementation which works reasonably
well where I’ve used it, but it’s worth considering existing ruby usage.
And the most common example I come across of a Ruby interface is
Enumerable.
The problems (as I see it) with the current implementation of Enumerable
are that:
1/ It doesn’t check for the existance of ‘each’ until you call a
method.
2/ It has brute force methods which could be implemented more
efficiently in specialised collections, e.g. Range, or a sorted
array.
3/ It supports reflection poorly. ie. there’s no easy way to look at
Enumerable and work out what a class needs as a pre-requisite
in order to use Enumerable usefully.
1/ Could be solved using Iface.append_features(aClassOrModule) to check
that the class defines ‘each’ as an instance method.
3/ Could be solved by making the interface keep track of the required
methods (which would help generically solve 1)
2/ Could be solved by allowing the interface to provide methods which
don’t override class methods of the same name.
There are also non-functional interfaces:
e.g. Marshal requires _dump and class._load to be defined.
The advantage of using a specified interface is that duck-typing isn’t
enough. And as language doesn’t have a infinite list of suitable
methods names, there’s the chance that there may well be a clash.
e.g.
class AmericanClothes
attr_reader :pants
end
class BritishPerson
def put_on_underwear(clothesSource)
if @underwear.nil?
if clothesSources.respond_to?(:pants)
put_on(clothesSource.pants)
else
raise ClothingError, “No suitable underwear”
end
end
end
end
Which is a tongue in cheek example, but the point is that there is no
universal atomic source of method names.
But an interface could exist in a global namespace, and thus guarantee
(as much as anything does in Ruby at the moment) that method :x does
what is expected of it, because the class declares it’s conformance.
Another reason for a clear interface definitions which I haven’t
noticed being mentioned is that for some purposes libraries need types
which are more complex than Ruby’s simple builtin types. The library
will either need a large number of respond_to? calls or a simple “does
it claim to conform to interface X?”. The advantages of interfaces as
proposed are twofold - for the programmer a clear definition of the
expectations as regard a complex object, for the library a simple check
for conformance. And regardless of what some detractors have said, I do
believe that this adds rather than detracts from the dynamism of Ruby -
so much Ruby code checks for kind_of?(IO) when it really means
implements?(IORead). With interfaces this could and should be clearer
and promote better programming practices.
Interfaces have unique, global names based on where they were defined.
They take the name of the class or module they were described in, but
they are not otherwise related to the class or module.
Aren’t/Couldn’t they be a specialised module?
Interfaces can be assigned to any class regardless of its proximity to
the interface description.
Once a class has been assigned an interface, it’s an error to change
the number or types of parameters described methods take.
Isn’t this an unlikely contingency to cater for? And more trouble than
it’s worth?
If there’s a library with an interface which accepts string arguments to
a particular methods, for example.
— in foo.rb
interface Foo
def bar(String title)
end
— in myprog.rb
class Bob
implements Foo
def bar(String|IORead title)
…
end
end
Couldn’t the programmer end up in the situation where they have to
modify a library (and either hope that the change is accepted by the
library author or distribute a modified version of the library) so that
they can have a class which behaves as they wish when used by their
code, but which is also accepted by the library?
With dynamic interface definitions this sort of thing could be worked
around, but if they were immutable then every application author would
have to rely on every interface using library author anticipating all
possible uses of their library.
You can still add methods any way you like, so long as they’re not
part of the described interface for the class.
See above.
When you subclass from a class with an interface, or mix-in a module
that has an interface, the subclass has that interface as well.
This is pretty much essential, but you’d get it “for free” if interfaces
were specialised modules.
You can re-implement methods in a subclass which has inherited an
interface, but the methods must have the same number and types of
parameters.
Ok - but I still don’t see why a program that knows what it’s doing
can’t take a class and use alias_method to insert hooks in a particular
function.
*** Syntax sugar
Parameter and return types are interface names.
If a method needs to enforce a parameter type check, objects passed to
that parameter must contain a “complete” flag for the interface
required.
Nice, but automated parameter type checking is not essential for a basic
interface implementation.
[snip]
Anything wrong with this way of doing it?
The main disadvantages I see are as follows:
1/ Compatibility
Libraries using this will not be usable earlier versions of
Ruby. As in they will break completely or displayed undefined
behaviour - this is a Bad Thing™ and will damage uptake.
(The great thing about so many of the changes between 1.6 and
1.8 is that many of them can be utilised but 1.6 still supported
by means of a compatibility layer written in Ruby - (I, for
example, still have a machine running 1.6 which I’m not in a
position to upgrade at the moment - hence pretty much anything
I write in Ruby for work needs to run on both).
2/ Dynamic extensibillity
I have a big problem with making interfaces immutable - it just
doesn’t feel right in Ruby. And greater certainty that the
object isn’t lying seems to be the only reason given for doing
it. (Ignoring the fact that the Object could be lie about the
return value of kind_of? anyway)
3/ Core interpreter
There are already various “core” features implemented in pure
Ruby - the most prominent being the Singleton mix-in. I don’t
see evidence supporting the notion that unless it’s done in
Ruby’s C source, interfaces won’t/can’t become a meaningful and
useful tool - I would agree though that they are unlikely to
have significant penetration unless an implementation is chosen
and included in the core distribution.
Hope this is of interest,
Geoff.
···
On Fri, Nov 21, 2003 at 06:08:39AM +0900, Sean O’Dell wrote: