Interface checking

B

Ben Giddings

I've been skimming over the thousands of emails about "stereotyping" for
the last few days, and I thought I'd step in now and say something.

First of all, please Austin and Sean, keep the personal attacks off the
list. There's no need for that. You both have strong views, and that's
great, but there's no need to expose the rest of us to your personal
attacks. (And Sean, that does include complaining about Austin's off-list
personal attacks as well). Just keep it professional on the list, please.

Now for the actual issue.

If I try to compile the following C program, I'll get some compile-time errors:

float convert(float temperature) {
return (temperature - 32) * 0.6;
}

int main(int argc, char **argv) {
printf("Temp is %d\n", convert(32.0));
printf("Temp is %d\n", convert(32));
printf("Temp is %d\n", convert("hot"));
return 0;
}

It will tell me that "hot" is an incompatible argument to "convert". In
this case, that's true. The C compiler does a great job of catching this
error. It is helpful because it identifies the function If "convert" were
declared in an external library and the documentation of it were somehow
inaccessible this is just the sort of information you'd want, before you
actually tried to use the program.

If you try a brain-dead simple Ruby implementation of the same thing, you
get an error, but it isn't nearly as informative:

def convert(temp)
(temp - 32.0) / 0.6
end

puts "Temp is " + convert(32.0).to_s
puts "Temp is " + convert(32).to_s
puts "Temp is " + convert("hot").to_s

foo.rb:2:in `convert': undefined method `-' for "hot":String (NoMethodError)
from foo.rb:7


If you use "duck typing", in the sense that you trust whatever is passed to
the "convert" method can be converted to a float, you get no errors. On
the other hand, the result probably isn't what you want.

def convert(temp)
(temp.to_f - 32.0) / 0.6
end
...

Temp is 0.0
Temp is 0.0
Temp is -53.3333333333333


Things are actually worse if you assume whatever is passed to the program
can be converted to farenheit:


def convert(temp)
(temp.to_farenheit - 32.0) / 0.6
end
...

foo.rb:2:in `convert': undefined method `to_farenheit' for 32.0:Float
(NoMethodError)
from foo.rb:5

The problem I see with all these errors is twofold:

1) There's no way to spot them until you actually call the "convert" method
2) The error message isn't very informative compared to the C error message
which tells you the name of the function and that you're passing an
inappropriate type.

Now, a lot of these problems can be overcome by making a less brain-dead
"convert" method, but IMHO that isn't the best strategy. That approach
requires the person writing the library method to be able to anticipate all
the ways in which someone might misuse the methods they provide, but at the
same time, not fail when the method is used in an unexpected way that would
actually have worked. Most library writers are either not smart enough
(me) or too lazy (me) to do that. I also don't think that testing is the
answer either, laziness and difficulty in writing complete tests being the
reason there too.

Obviously, for Ruby the solution isn't to implement static type checking.
The general solution isn't to check the class of the argument inside the
method either. In a dynamic language like Ruby that's either too
restrictive or prone to error. I don't think checking "respond_to?" is a
solution either. Take the case above where I ended up doing "hot".to_f.
It responds to that method, but it is unlikely that the person passing
"hot" in as an argument wanted it interpreted as 0 farenheit.

If we want to get from Ruby what we get from static type checking in C, we
need a way to get the two things above: the name of the method that failed
and the reason it failed.

Consider the error message you get in the first example:

foo.rb:2:in `convert': undefined method `-' for "cold":String (NoMethodError)

The problem I have with this error message is that it isn't clear that
"cold" is the argument to the method, and it isn't clear why exactly it
failed. The error message says that '-' is an undefined message, but (at
least to me) that doesn't translate to "you passed me a String but I was
expecting something like a number".

Now maybe it is possible to modify Ruby so that when you define a method,
it automatically wraps it in a begin/end block that catches NoMethodErrors
and, if it determines that the object that caused the NoMethodError is an
argument to the method it modifies the error to say something like:

foo.rb:2:in `convert': undefined method `-' for "cold":String; Possibly a
an illegal argument to convert? (NoMethodError)

What would make things even more interesting is if somehow it were possible
to find out what methods an object is expected to implement in the course
of a method, so given a method like:

def convert(temp)
if temp.respond_to?:)to_farenheit)
(temp.to_farenheit - 32.0) / 0.6
else
(temp.to_f - 32.0) / 0.6
end
end

It might know that temp needs to implement "respond_to?" and one of
"to_farenheit" or "to_f". Then you could get an error message like:

foo.rb:2:in `convert': undefined method '-' for "cold":String; Possibly a
an illegal argument to convert? Required methods: [:respond_to?,
:to_farenheit] or [:respond_to?, :to_f] (NoMethodError)

I think that would be incredibly useful, but most likely very, very
difficult to implement.

Anyhow, there's still the other problem: C finds argument-type errors at
compile time, Ruby finds them at runtime. Is there a way to have Ruby find
them immediately rather than when the method is called? Because of the
dynamic nature of Ruby, the only possible answer to this is 'no'. But,
having said that, there are other possibilities.

When you run ruby with the '-d' flag set, it prints to stderr all
exceptions, whether caught or not. This means that it often prints things
to stderr that don't need to be there. Maybe it is possible to come up
with a similar type checking mechanism. At "compile time" (whatever that
means in an interpreted language) it would try to look for type
inconsistencies, meaning arguments to methods that don't implement the
required methods. It could print those out as warnings, then continue,
hoping that by the time it actually calls those methods, things will have
been resolved. How this would be done? I have *no* idea. It sure sounds
difficult, but it would sure be useful.

Now even of all the above could be done, there would still be errors. Just
because a method exists doesn't mean it does the right thing. A method
named 'read' might be a verb telling an IO-type object to read something,
or it might represent a boolean flag indicating a message-type object has
been read.

Maybe the easy solution for the short term is to do like Python and emacs-lisp:

def foo(bar)
"Foo takes a 'Bar' as a parameter. It will try to do X, Y and Z with it"
...
end

irb> foo("hot")
in `convert': undefined method `-' for "hot":String (NoMethodError)
irb> foo.docstring
"Foo takes a 'Bar' as a parameter. It will try to do X, Y and Z with it"
irb> foo(25.0)
5.0
irb>

This docstring type functionality wouldn't help much when running programs
on the commandline, but it would be really useful when debugging or poking
at things with irb.

Comments?

Ben
 
S

Sean O'Dell

I've been skimming over the thousands of emails about "stereotyping" for
the last few days, and I thought I'd step in now and say something.

First of all, please Austin and Sean, keep the personal attacks off the
list. There's no need for that. You both have strong views, and that's
great, but there's no need to expose the rest of us to your personal
attacks. (And Sean, that does include complaining about Austin's off-list
personal attacks as well). Just keep it professional on the list, please.

Appeal to the negativity I've received while trying to help work through this
idea. I'm a perfectly civil person, but I don't allow people the freedom to
continually harry me. I will grant everyone this respite, though: instead of
responding with "whatever you say Ziegler" I will simply ignore him.

The rest of your comments are terrific, and I hope they generate some change
ideas!

Sean O'Dell
 

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,981
Messages
2,570,187
Members
46,730
Latest member
AudryNolan

Latest Threads

Top