Object-Orientedness vs. Duck Typing
Yesterday I was thinking about object systems and the difference between methods and functions when I stumbled upon a very strange conclusion. It seems to me that any object-oriented system is fundamentally just a little at odds with duck typing. Clearly it’s possible to reconcile the two; both Ruby and Python do a fine job. But there’s a very interesting tension there.
To see what I mean, think about the difference between a method of an object and an ordinary global function. Really all a method is is a function that takes an instance of an object to be the “self” of the function.
This can be done explicitly, as with Python’s “self” argument for every method, or implicitly, as with Ruby’s “self” keyword and scope. You can even do it explicitly in Ruby if you want. For example, the two methods below are the same in everything but syntax:
class Array def middle self[length / 2] end end def middle(this) this[this.length / 2] end [1, 2, 3].middle #=> 2 middle([1, 2, 3]) #=> 2
Except they’re not quite the same. On Arrays, sure, they’re identical. But what happens if you give them something they don’t expect?
{:foo => "bar"}.middle #=> NoMethodError: undefined method `middle' for {:foo=>"bar"}:Hash
middle({:foo => "bar"}) #=> nilWoah! That’s pretty different!
When you call the normally-defined method middle on the Hash,
it throws a NoMethodError, because middle isn’t defined for Hash.
When you call our middle, though it return nil.
Why? Well, {:foo => "bar"}.length is 1,
so it’s looking for {:foo => "bar"}[1 / 2]
which is the same as {:foo => "bar"}[0].
Since there’s no key 0, it returns nil.
The trick here is that I actually excluded a very important aspect of methods from my Ruby definition. Not only do all methods take a “self” argument, but they all also type check it. Sure, the first example raised a NoMethodError, not a type error, but consider the following definition:
def middle(this) unless this.is_a?(Array) raise NoMethodError.new("undefined method `middle' for #{this.inspect}:#{this.class}") end this[this.length / 2] end
Now our middle acts exactly like the normally-defined one:
{:foo => "bar"}.middle #=> NoMethodError: undefined method `middle' for {:foo=>"bar"}:Hash
middle({:foo => "bar"}) #=> NoMethodError: undefined method `middle' for {:foo=>"bar"}:HashWhat does this mean? It means that every single method of every object system is just a little bit statically typed. The “self” argument will always be the type you expect it to be; there’s no way to duck around it it all.
Well, not quite no way. Ruby’s very clever about this. It provides a mechanism for creating methods where the “self” argument is duck typed: modules. Modules can have methods that reference “self” or even other undefined methods, but they’re never instantiated. Instead, they’re included in other classes. Then the “self” that was referenced in the methods becomes the instance of the class, and Ruby becomes totally duck typed.
About Me
Quines II


