This post originated from an RSS feed registered with Ruby Buzz
by Rick DeNatale.
Original Post: Duck A La Range
Feed Title: Talk Like A Duck
Feed URL: http://talklikeaduck.denhaven2.com/articles.atom
Feed Description: Musings on Ruby, Rails, and other topics by an experienced object technologist.
On ruby-talk, Sammy Larbi recently asked if it would make sense to add a length method to Range, something like this:
class Range
def length
self.end - self.begin
end
end
This led to an interesting discussion about the varieties of ranges, and ultimately led me to this article about duck types.
This led to an observation by Xavier Noria that
Ranges don't require their elements to support a "-" method, only :succ and :<=>, so this wouldn't work for all ranges.
A first approximation of a general implementation of Range#length would be to iterate over the elements of the range and count them but..
Some ranges might actually have an infinite number of elements, so length doesn't make sense for them.
Home, Home on the Infinite Range
In thinking about this a bit, I'm not sure that you can have a Range with an infinite number of elements which supports enumeration. Xavier gave a range of rational numbers as an example, but I'm not sure I see how you can define both a :<=>, and :succ methods for rational numbers which make sense. The problem is that although rational numbers are countable, they are also densely ordered.
The fact that rationals are countable simply means that you can pair each rational number with an integer. One proof of this constructs the sequence: 1/1, 2/1, 1/2, 1/3, 3/1, 4/1, 3/2, ...
Now this shows that you can put the rationals into an infinite sequence, and define :succ such that (1/1).succ => (2/1), but if you want to keep the normal meaning of :<=>, you have the problem that 2/1 looks like it is less than 1/2.
The fact that rationals are densely ordered means that for any two rationals a <. b there are an infinite number of rationals, c, such that a <. c < b. Which makes it hard to pick which one should be a.succ.
Now it might be possible to come up with an enumerable infinite range which works, but it hurts my head, and it's not really the main point of this article.
What About Non-enumerable Ranges?
The rdoc for Range includes this:
Ranges can be constructed using objects of any type, as long as the objects can be compared using their <=> operator and they support the succ method to return the next object in sequence.
This is true, if you want to use all of the methods of Range, but one can actually usefully create ranges of objects with support <=> but don't support succ.
Floats respond to <=>, but they don't respond to succ. As shown above you can create a Float range, and use it to see if a number falls within that Range. What you can't do is enumerate such a Range:
(1.0..2.0).to_a # =>
# ~> -:3:in `each': can't iterate from Float (TypeError)
# ~> from -:3:in `to_a'
# ~> from -:3
So what does this have to do with duck typing? It just goes to serve as another illustration of why classes make lousy duck types. It's not just a matter of what an objects class is, in general it's a matter of what the object can do at the time, and that can depend on things other than the class and the methods. The duck in this last example is a bird which can determine whether or not it includes a number, it simply has to quack yes or no when fed a number. It doesn't have to walk along all the numbers it 'contains.'
Whether or not such a duck is useful depends on the user of the duck, not the duck itself.
About That Picture
I searched around to find a non-copyrighted photo of Duck a l'orange, and couldn't find one. I did find one of a Turducken. For those unfamiliar with this bird, it's a boneless turkey, which has been stuffed with a boneless duck, which has in turn been stuffed with a boneless chicken. After thinking about this a bit it seemed appropriate for this article which is after all about the effects of stuffing one object with another.