Thinking About Java Interfaces In Ruby

  • Thread starter James Edward Gray II
  • Start date
J

James Edward Gray II

I'm currently reading "Holub on Patterns", an excellent volume on
Object Oriented Programming Voodoo. The language of the book is Java
and it makes extensive use of Java's Interfaces to avoid "The Fragile
Base Class Problem".

This has me thinking a lot about proper Ruby coding techniques. What
is Ruby's equivalent to a Java Interface?

module Interface
def some_method; end
def another_method; end
end

That doesn't really require you to provide those methods in a class
that mixes them in. Perhaps:

module Interface
def some_method
raise NoMethodError, "Must be overriden!"
end
def another_method
raise NoMethodError, "Must be overriden!"
end
end

Of course, I guess the only point of something like this is type
checking, which Java needs when it declares a variable, but Ruby does
not. Given that, client code can easily check interface:

def my_method( interface )
interface.respond_to?:)some_method) or raise ArgumentError, "Invalid
interface."
interface.respond_to?:)another_method) or raise ArgumentError,
"Invalid interface."

# ...
end

But that probably doesn't gain you much over just trying to call the
methods and throwing exceptions that way, I guess. Which brings us
full circle to doing nothing at all.

Does Ruby combat The Fragile Base Class Problem with philosophy alone?
I'm referring to Duck Typing here, of course. Let them pass in what
they want and if it responds to all the right messages, we don't care
what it is. In this scenario, we're not supposed to check "kind_of?"
at all, right? In that case, how do you document a method like this?

class DatabaseTable
# ...

def export( exporter )
exporter.start_table
exporter.store_metadata(@name, @width, @column_names)
@rows.each { |row| exporter.store_row(row) }
exporter.end_table
end
end

You don't really just say, "Pass in an object that responds to X, Y,
and Z." in the RDoc, do you?

The "exporter" argument should probably be a type, it seems to me. I
guess in this case a module with empty methods is a fine choice. Then
you can just override what you need. But wait, isn't that just a "Base
Class"??? Might as well use a class then...

In case it isn't obvious, this message is mostly just me thinking out
loud. However, I'm very interested in the opinions of others, which is
why I'm thinking out loud on Ruby Talk.

I figure I'm probably just too much in Java mode right now (sorry, it's
the day job) so that I can't see the Ruby answers. If you're reading
my babble and thinking "You wouldn't do it like that in Ruby!",
criticize away. You won't hurt my feelings.

James Edward Gray II

P.S. Book writers, are you listening?! I'm crying out for a Ruby OO
Voodoo book!!!
 
L

Lothar Scholz

Hello James,

JEGI> You don't really just say, "Pass in an object that responds to X, Y,
JEGI> and Z." in the RDoc, do you?

Unfortunately yes. And it is of course a huge source of errors.
It would be much better if ruby would support formally interfaces in
the same way as Smalltalk supports protocols. These interfaces are
notation only and are only used by the IDE to check if a method is missing.

You can't do something against passing a wrong object in a dynamically
typed language, but you can do something against missing methods
during implementation.

Remember that the problems grow if the interface changes and
you have old ruby code, since even if you write test cases you will
normally not catch all those errors, leaving this upto your customers.

This is why many people think that languages like ruby are not good
for programming in the large.
 
F

Francis Hwang

Does Ruby combat The Fragile Base Class Problem with philosophy alone?
I'm referring to Duck Typing here, of course. Let them pass in what
they want and if it responds to all the right messages, we don't care
what it is. In this scenario, we're not supposed to check "kind_of?"
at all, right? In that case, how do you document a method like this?

class DatabaseTable
# ...

def export( exporter )
exporter.start_table
exporter.store_metadata(@name, @width, @column_names)
@rows.each { |row| exporter.store_row(row) }
exporter.end_table
end
end

You don't really just say, "Pass in an object that responds to X, Y,
and Z." in the RDoc, do you?

In the RDoc, you usually say "Pass in an object of X class." RDoc
comments ideally are a sort of loose contract, so when you say that
you're implying "You can poke around with other classes, but there's no
guarantee this library won't blow up when you do it, and there's no
guarantee the internals won't change in the future so that a hack that
works today will break tomorrow."

It's a philosophical difference, and it's fairly profound. Heavy OO
theory seems to believe more or less in Platonic ideals: There are
universal types that exist in the universe, and if you do a lot of
domain research up front you'll discover them all and won't have to
refactor much as you implement. (This is comparable, incidentally, to
Islamic illustrators of antiquity, who were not allowed to make
drawings that mimicked nature, so every time they drew, say, a horse,
they would say they weren't drawing a particular horse in the world,
but the essence of all horses.)

Lightweight OO theory -- expressed in the idioms of Ruby and other
light OO languages like Smalltalk -- says that types are provisional,
and to a certain extent, they're just convenient lies we tell ourselves
to get our work done. (If you want, you can tie this into certain
strains of Buddhism which argue that the self is an illusion we hold up
to get through the day.) You define your classes as you need them, but
the time may come when you discover that the class needs to be
redefined or discarded entirely, so you do what is necessary for the
moment and move on.

Note that most of this is untenable without rigorous unit testing. And
in fact, the recent popularity of Ruby and *cough* Python owe more than
a little to the bubble in XP and Agile methodologies a few years back.
People started unit-testing in Java, then they realized that once they
were writing the tests, all that static typing caused more harm then
good.

On a level of expressiveness, I think one of the nice consequences of
unit testing is the fact that you can encode a lot of subtle
assumptions into those tests. As opposed to trying to write a class and
interface, that's not going to encode nearly as much.

F.
 
G

gabriele renzi

James Edward Gray II ha scritto:
I'm currently reading "Holub on Patterns", an excellent volume on Object
Oriented Programming Voodoo. The language of the book is Java and it
makes extensive use of Java's Interfaces to avoid "The Fragile Base
Class Problem".

This has me thinking a lot about proper Ruby coding techniques. What is
Ruby's equivalent to a Java Interface?

module Interface
def some_method; end
def another_method; end
end

That doesn't really require you to provide those methods in a class that
mixes them in. Perhaps:

module Interface
def some_method
raise NoMethodError, "Must be overriden!"
end
def another_method
raise NoMethodError, "Must be overriden!"
end
end

Of course, I guess the only point of something like this is type
checking, which Java needs when it declares a variable, but Ruby does
not.

notice that there is a Interface module that allows you to have
interfaces a-la java. See here:
http://ruby-miscutils.sourceforge.net/interface.txt
(yeah ruby can become most anything :)
Given that, client code can easily check interface:

def my_method( interface )
interface.respond_to?:)some_method) or raise ArgumentError, "Invalid
interface."
interface.respond_to?:)another_method) or raise ArgumentError,
"Invalid interface."

# ...
end

But that probably doesn't gain you much over just trying to call the
methods and throwing exceptions that way, I guess. Which brings us full
circle to doing nothing at all.

Imo it gives you both a crash-early and a documentation hint, wich are
valuable things on their own. I'd like to see optional type hints in
ruby, even because I'd love to have generic methods or predicate dispatch.
The problem is that either they're checked at runtime, thus slowing down
the code or they're handled with some complex partial evaluation at
compile time (I think some scheme/lisp do this)


Does Ruby combat The Fragile Base Class Problem with philosophy alone?
I'm referring to Duck Typing here, of course. Let them pass in what
they want and if it responds to all the right messages, we don't care
what it is. In this scenario, we're not supposed to check "kind_of?" at
all, right? In that case, how do you document a method like this?

class DatabaseTable
# ...

def export( exporter )
exporter.start_table
exporter.store_metadata(@name, @width, @column_names)
@rows.each { |row| exporter.store_row(row) }
exporter.end_table
end
end

You don't really just say, "Pass in an object that responds to X, Y, and
Z." in the RDoc, do you?
The "exporter" argument should probably be a type, it seems to me. I
guess in this case a module with empty methods is a fine choice. Then
you can just override what you need. But wait, isn't that just a "Base
Class"??? Might as well use a class then...

maybe you can define a module Exporter or an AbstractExporter class and
document that, but it's not a real solution, I think.
But it does document things, and whenever a user of DatabaseTable look
at it he can just figure out 'oh, well, I have to define this, no need
to import.. '

In case it isn't obvious, this message is mostly just me thinking out
loud. However, I'm very interested in the opinions of others, which is
why I'm thinking out loud on Ruby Talk.


Everubody come to think of this sooner or later :)
I even wrote an rcr wich is related : http://www.rcrchive.net/rcr/show/280


I figure I'm probably just too much in Java mode right now (sorry, it's
the day job) so that I can't see the Ruby answers. If you're reading my
babble and thinking "You wouldn't do it like that in Ruby!", criticize
away. You won't hurt my feelings.

I hope not :)
anyway, I think problems such "I will break stuff whenever I modify
something and don't have a clear documented interface!" is mostly solved
from Test::Unit. You're writing tests anyway, don't you? :)

James Edward Gray II

P.S. Book writers, are you listening?! I'm crying out for a Ruby OO
Voodoo book!!!

+1
 
D

Dave Thomas

Hello James,

JEGI> You don't really just say, "Pass in an object that responds to
X, Y,
JEGI> and Z." in the RDoc, do you?

Unfortunately yes. And it is of course a huge source of errors.

Strange - that's not my experience.


Cheers

Dave
 
J

James Britt

Dave said:
Strange - that's not my experience.

Nor mine. But there are some number of people who believe this to be true.

In some cases, they believe this because they heard it from a source
they trust. But for those who actually experience this, I wonder if
there is something about their coding/design process that differs from
those who find duck typing largely trouble-free.

Put another way, some find duck typing "a huge source of errors", others
do not. I do not think anyone is lying, so what accounts for the
difference? Class/method design? Unit testing? LOC?

James
 
E

Edwin Eyan Moragas

Nor mine. But there are some number of people who believe this to be true.

nor mine. so far.
Put another way, some find duck typing "a huge source of errors", others
do not. I do not think anyone is lying, so what accounts for the
difference? Class/method design? Unit testing? LOC?

i think the source of "huge errors" would be in the overall
programming experience.
when i was young and full of hope, coding in c and perl was such a
pain primarily because
i didn't know how to write tests then.

shifting from php/perl to java when i grew a bit older in the code
gave me a really good
feeling because my IDE can check syntax even without running things i wrote.

then i as grew even older in the code, java just couldn't hack it
anymore and i found
ruby. but this time, i have learned how to write tests and i never felt an ounce
of unease just because i can't check syntax while writing.

so there. IMHO, duck typing is good because i don't have to go thru hoops just
to get my new class accepted. but the insurance comes from good tests.
another great
benefit is the lesser number of lines of code to do equivalent tasks.

ask paul graham might say "succinctness is power". you might want to
read his thoughts on java too. :)

now back to my day job...
 
A

Austin Ziegler

You don't really just say, "Pass in an object that responds to X,
Y, and Z." in the RDoc, do you?

Yes, indeed, I do. If I want something that responds to #<<, I'll
say that.

The only reason I really see for having interface hinting is
automatic cross-language interface discovery (e.g., SOAP, CORBA,
etc.). This way, you can simply say "this class is available for
SOAP manipulation" and SOAP can discover what it expects.

I haven't missed interfaces in the way that they are required for
less dynamic languages.

-austin
 
F

Francis Hwang

shifting from php/perl to java when i grew a bit older in the code
gave me a really good
feeling because my IDE can check syntax even without running things i
wrote.

then i as grew even older in the code, java just couldn't hack it
anymore and i found
ruby. but this time, i have learned how to write tests and i never
felt an ounce
of unease just because i can't check syntax while writing.

I suppose one of the things that becomes evident in a learning process
like this is that there are two sorts of correctness: Syntactic (does
it compile?) and Semantic (does it do what it's supposed to do?) It's
trivial to write code that is syntactically correct but semantically
gibberish. It's the second you have to worry about, and a compiler
can't help you with that stuff.

F.
 
J

Jamis Buck

Austin said:
Yes, indeed, I do. If I want something that responds to #<<, I'll
say that.

The only reason I really see for having interface hinting is
automatic cross-language interface discovery (e.g., SOAP, CORBA,
etc.). This way, you can simply say "this class is available for
SOAP manipulation" and SOAP can discover what it expects.

I haven't missed interfaces in the way that they are required for
less dynamic languages.

-austin

I'm completely with Austin on this. I do the same thing: "this method
requires a method that responds to #foo". It has served me well enough.

The *only* time I've thought interfaces would be nice is when I
considered finding a way to implement type 1 inversion of control. This
is where a service just says "I need another service that implements
this interface" and the container goes and finds one that matches.

Even then, I was only thinking it would be neat to implement for the
"gee-whiz" value, not because I actually needed that feature.
 
F

Francis Hwang

The *only* time I've thought interfaces would be nice is when I
considered finding a way to implement type 1 inversion of control.
This is where a service just says "I need another service that
implements this interface" and the container goes and finds one that
matches.

Even then, I was only thinking it would be neat to implement for the
"gee-whiz" value, not because I actually needed that feature.

If you want to be really far out, you can write code that infers this,
by looking to see if various methods are defined. I've done this in
extremely simple cases, and I wonder if there's a use for the idea in
broader contexts, but that sort of feels like PhD stuff.

F.
 
E

Edwin Eyan Moragas

I suppose one of the things that becomes evident in a learning process
like this is that there are two sorts of correctness: Syntactic (does
it compile?) and Semantic (does it do what it's supposed to do?) It's
trivial to write code that is syntactically correct but semantically
gibberish. It's the second you have to worry about, and a compiler
can't help you with that stuff.

right on.
 
M

Michael DeHaan

Yep, Weak-typing isn't a source of errors for me either.

Much of the java development cycle is spent casting and boilerplating,
IMHO, and for me, that *is* a source of errors -- but I do not find
instances in Perl or Ruby where I have an array of gerbils and a
giraffee accidentally sneaks in. That just doesn't happen very
often, and when it does, it's so rare I'm ok with the runtime
exception.

Up front checking on some things may be nice, but there are certaintly
a reason I am coding in a weakly typed language -- namely, they are
still fun to program in and there is much less boilerplate.
 
J

Jamis Buck

Francis said:
If you want to be really far out, you can write code that infers this,
by looking to see if various methods are defined. I've done this in
extremely simple cases, and I wonder if there's a use for the idea in
broader contexts, but that sort of feels like PhD stuff.

True, and I considered that (the approach, not the PhD :p). However,
most of the time, the interface that is desired is only a small subset
of the methods published by the object...

Also, things are further complicated by the fact that you can't just
look at a service definition and determine its interface by reflection.
This is because the service definition (in Needle) is what is returned
by executing a block, and I'd like to be able to get the interface
information without having to execute the block (and thereby actually
instatiate the object).

I've considered having service declarations explicitly name the methods
they export, but that's where things just become too much work. Besides,
"interface injection" just doesn't feel very Rubyish, especially since
interfaces in Ruby can be changed on the fly, and may even differ from
one object to the next, even though they share the same instantiating class.

- Jamis
 
L

linus sellberg

module Interface
def some_method
raise NoMethodError, "Must be overriden!"
end
def another_method
raise NoMethodError, "Must be overriden!"
end
end

Another way, which I personally feel is neatier (if nothing else because
the error message comes a lot earlier) is to check in the constructor:

Class base_class
def initalize
raise 'Method required: MethodA' unless (respond_to? :MethodA)
raise 'Method required: MethodB' unless (respond_to? :MethodB)
end
end

It will break if the constructor is overridden without calling the one
of the superclass though.
 
G

gabriele renzi

Jamis Buck ha scritto:


I'm completely with Austin on this. I do the same thing: "this method
requires a method that responds to #foo". It has served me well enough.

well, but once you write it up as documentation would'nt you be happy if
actually there was a check for this and the doc can be extrapolated
automatically?
(ok, ok automagic interface inference is impossible but it sounds so cool :)
 
L

Lothar Scholz

Hello Michael,

MD> Yep, Weak-typing isn't a source of errors for me either.

Most of my errors came from the time when i had to work on dynamically
typed source code (Python in this case) that came from other persons.
Its a real pain to find out what types are expected if you haven't
written the code yourself. Maybe on the next project i have some time
to find out more by using Humphrey Watts "Personal Software Process".
 
F

Francis Hwang

True, and I considered that (the approach, not the PhD :p). However,
most of the time, the interface that is desired is only a small subset
of the methods published by the object...

Also, things are further complicated by the fact that you can't just
look at a service definition and determine its interface by
reflection. This is because the service definition (in Needle) is what
is returned by executing a block, and I'd like to be able to get the
interface information without having to execute the block (and thereby
actually instatiate the object).

I've considered having service declarations explicitly name the
methods they export, but that's where things just become too much
work. Besides, "interface injection" just doesn't feel very Rubyish,
especially since interfaces in Ruby can be changed on the fly, and may
even differ from one object to the next, even though they share the
same instantiating class.

I assume you don't want to instantiate the service because
instantiation is expensive? How about just instantiating it once,
getting the methods, and then saving them for later? (what's below is
not tested, I'm just thinking in code ...)

service_class = SomeReallyExpensiveService
service = service_class.new
my_methods = service.methods
File.open( "~/.needle/#{ service_class.name }.methods.yaml", "w" ) { |f|
YAML.dump( my_methods, f )
}

Then whenever Needle is wrapping that service, you can look for the
YAML file to see if you've got the methods pre-processed. It's like
post-compilation!

Holy crap, do I love reflectivity.

Anyway, it's true that this all falls apart if you're adding
per-instance methods to a service. Personally, I really don't like
adding per-instance methods: For all my loosey-goosy typeless talk, I'm
sort of a chicken about that stuff and I like all my objects of one
class to respond to the exact same set of methods. But that's just me.

F.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top