From: Gavin Sinclair Date: 2002-09-29T14:29:33+09:00 Subject: Re: thoughts on typelessness Good words, David. My responses are interleaved below. ----- Original Message ----- From: > 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