Good words, David. My responses are interleaved below.
Hi –
The on-going discussion of explicit typing and Ruby has whetted my
appetite for traveling further into the typeless universe But
mainly, it’s got me trying to think fairly deeply and precisely into
the question of why this notion keeps coming back, in spite of its
fairly manifest (and, I think, pretty universally acknowledged)
extrinsicness to Ruby. These comments, then, pertain to typelessness,
and to the question of why some people’s embrace of the Ruby model is
equivocal. (They’re not specific to the ‘R’ initiative, though
obviously that’s been at the center of recent debate.)
Some people feel safer with explicit types. There are good subjective reasons
for this:
- computing should be explicit
- you need greater rigidity as projects get large
- most people have a backgound in explicit typing, and those
languages have explicit types for good reason
In university, one of the core things I learned in first-year computer science
was “abstract data types” - learning to understand and implement lists, trees,
hashes, etc. This is obviously important to know, and it implies the
importance of strong types, and knowing what you’re dealing with.
There’s a scene in Buster Keaton’s film “Sherlock, Jr.” where Keaton
is on an out-of-control motorcycle, about to come to a huge gap
between two stretches of elevated road – i.e., he’s about to plummet.
But two trucks come along on the road underneath, from opposite
directions, and converge for just an instant in precisely such a way
that Keaton can glide across their tops and onto the road on the other
side of the gap. A second earlier or a second later, he would indeed
have plummeted – but for just that instant, the necessary components
were in place.
Ruby objects remind me of that scene. They are what they are at a
given moment, whether or not that’s what they were or what they will
be. And in their momentary permutations and capabilities, they can do
some very powerful things.
And they can just miss the trucks, plummet to the road below, break their arms
and legs, and then get run over by the truck. Then they get sent to hell
instead of heaven because some SOB redefined their finalizer.
Building projects that even potentially rely on trucks coming together can be
described in the following ways: creative, interesting, risky, irresponsible,
crazy, brilliant.
All of this is so enthralling to me that I’m always surprised when
people advocate explicit typing (in one form or another) for Ruby. It
seems that, in spite of everything (including the existence of other
languages for those made queasy by typelessness), a number of Ruby
users are more drawn to type-based programming than away from it. I
wonder why this is.
The fundamental reason I hear most often is the desire to weed out bugs at
compile-time, not run-time. This will be argued by many on the list, myself
included, as something of a fallacy. However, these arguments tend to rely on
the primacy of unit testing, rather than proving correct designs. There is
nothing necessarily right ot wrong about this, but it is inconsistent with what
we learnt, and came to agree with, at university. (I don’t know what unis
teach nowadays.) Most of us, at some point, have to ask whether Ruby really
concurs with what we think is “right”. I’d be interested to know if and how
you traversed these crossroads, David.
One thing’s for sure: “dynamic programming” leaves a lot of risk at run-time.
It is a matter of continuing debate how much of this risk can be ameliorated.
As I’ve said before, Ruby is an experiment, like non-Euclidean geometry. Dave
Thomas exhorts those with queasy stomachs to try writing seriously large Ruby
applications and seeing if their fears materialise.
Ruby introduces (to my radar, at least - I’m sure Smalltalkers will disagree) a
new paradigm for programming. As this gains greater acceptance, traditional
academics will have to focus some research efforts on it. By the looks of it,
Ruby can ride the wave of interest in XP, and maybe benefot from research into
that, but that’s only have the story.
Until such research is carried out, and time allowed for the flow-on effects
(articles, education, …) we are bound to have the same debates time and
again. Every day, someone new is having to ask themselves whether and how to
embrace a “typeless” language.
I’d like to see, as a crystallisation of discussions on this list, a document
prepared outlining the costs and benefits of Ruvy programming. Something to
make very interesting reading for Java programmers. Of course, I’m not putting
my hand up, and do not have the necessary experience anyway.
I don’t have a particular or conclusive answer, just some thoughts.
Mainly these have to do with Ruby methods and syntax, rather than
other factors (like whether managers will accept a dynamically typed
language).
One possible factor, I think, is the #type method. As I mentioned on
IRC this evening, I sometimes wish Ruby didn’t have it. It’s harder
to make the argument that objects don’t have types, when the objects
themselves willingly tell you their types… And I might add
#is_a? and #kind_of? to the list. Maybe they’re necessary for
reflection. It’s interesting to try to program without them.
You should try to program without them, but it may not always be impossible or
undesirable. (To suggest this is surely to place yourself near a far end of
the spectrum in this debate, David.)
I think types will always be important in computer programming. Anyone care to
disagree? The problem we have in Ruby is how to define them. They’re so hard
to pin down. Ruby still has cookie-cutters (classes), but both the
cookie-cutters and the resulting cookies can grow new arms and new legs,
meaning that neither of them is wholly suitable for defining something as
significant and universal as a “type”. You put it best, Davis when you said
that a Ruby object is what it is.
However, in practical programming (remember that? it is sometimes important
to know what you’re dealing with. It is somethimes desirable to allow a method
to act on either an Array or Hash or String or …, and exhibit sensible but
different behaviour based on this. The writer of the method thus assumes:
- you will not overly distort the class Array, String, …
- you will not overly distort objects of said classes
- you will want to use objects of these classes rather than objects
of some other class that is vaguely similar to an Array, for instance
These assumptions generally hold true for the important built-in classes like
Array and String. They will start to break down when you define and distort
your own objects. Therefore, a blurry line can be drawn where such code as
“case x when Array …” etc. starts to become poor programming.
Another thing people often point to that might deter people from
learning a truly type-unchecked programming style is the wordiness of
the most often-mentioned alternative, the “respond_to?” test:
thing.action if thing.respond_to?(action)
This has the look and feel of duplicate, workaround code. Whereas
this:
raise ArgumentError unless obj.is_a?(Thing)
doesn’t, and neither does this:
case obj
when /String/ …
when /Array/ …
(And of course this:
def meth(String s)
wouldn’t either
So one problem might be that while the focus is on what an object will
respond to, the #respond_to? test itself is by no means the most
concise or elegant available idiom. This means that testing for type
offers a path of less resistance – so people take it.
The price of liberty is eternal vigilance. This applies to Ruby programming
(and especially design) as much as it does walking alone at night in large
American cities. People have to decide what balance they want to achieve
between order and chaos in Ruby programs, and then they have to enforce it.
(This is the heart of the typeless debate.)
Asking an object if it responds to :xyz is deeply unsatisfying. Even if it
does, you have no idea what that method will do. The English language is full
of homonyms, so Ruby objects have the potential to be as well.
Therefore, querying the class of an object not only is the path of least
resistance, it gives you more assurance of what you’re actually dealing with.
It may be a blunt instrument, and one may have reservations about using it, but
in some situations its all we have.
Raising ArgumentError is a way to ensure that you get a sensible error message.
A common complaint is that unwelcome objects make their way deep into methods
before their identity is finally uncovered by dint of not responding to a
method. If you are somehow undure of your code, it’s better to find this out
sooner rather than later.
Also, consider this. If you do raise ArgumentError because an object is the
“wrong” “type”, and later find this is unnecessarily restraining you, you can
liberalise the method later. The point is, you’ll easily find out. If you’d
been liberal in the first place, you may have suffered a cryptic error. In
Ruby, it’s easy to build walls and break them down as needed.
(Of course, in looking at behavior in Ruby as fundamentally per-object
and dynamic, testing with #respond_to? isn’t conclusive anyway, since
the response in question may or may not be what one wants. But it’s
on the right path, because it’s addressing the present of the object,
not its past.)
You can also do this:
begin
a.meth
rescue NameError
…
I’ve done that, for instance, in my dbdbd (database definer) program.
It uses 1-originating array notation to index columns, but can also
use field-names. So the reader method for the sort_key (for example)
is:
def sort_key
@sort_key - 1
rescue NameError
@sort_key
end
which I think looks better than:
def sort_key
if @sort_key.respond_to?(
@sort_key - 1
else
@sort_key
end
end
or any of the many variants of that. But the rescue version is
relatively slow.
It does look better. But what if the object responds to :- but doesn’t do what
you want. If you’re lucky, your program wil break cleanly. Otherwise, you’ll
end up with funny indexing of critical data committed to a file. I’m only
playing devil’s advocate here.
There’s also been talk of a sort of conditional method call, where the
call would only happen if the object responds to the method. The
problem with that is: what happens if it fails? You can’t just return
nil or false, since those might be what the successful method call
would have returned.
I don’t have any conclusive, ummm, conclusions to draw. Just
wondering what people think of all of this. There was talk briefly of
the idea of multi-dispatch and method signatures on respond_to?
criteria, rather than type. Maybe that’s a way to bring that more
accessibly and concisely into the syntax. (Though I’m not sure
exactly what that way would consist of, syntactially.)
I don’t support the method dispatch stuff, and side with you on most aspects of
this issue. To me, the dispatch stuff is too wordy for to little return. I’m
not passionate in opposition to it, though; it could be useful, but it could
change the language somehow, and cause people to write lots of ugly code.
David
–
David Alan Black | Register for RubyConf 2002!
Gavin
···
----- Original Message -----
From: dblack@candle.superlink.net