Language ninja, Ola Bini made a remark that Ruby was strongly-typed. Without confusing “strong” with “static”, this still made me pause.
I knew it was type-safe. I knew it was dynamically-typed. I knew it used Duck typing. I didn’t think about it in terms of strong types. I suppose arithmetic exceptions and array-bounds checking make a language strongly typed (and we all like that) but I would have put this more into the “type-safe” category.
What I didn’t think about so much is that Ruby does also raise type conversion exceptions and it raises exceptions if the object (ultimately) doesn’t accept the message it is sent (two other important aspects to what most people mean by “strongly typed”). It’s obvious - and if you’ve ever tried to add a number to a string, or vice versa, in Ruby then you have met the strong typing of Ruby.
Eli Bendersky has a good taxonomy of typing systems which I encourage you to read (as well as the comments).
I now figure that Ruby is strongly-typed just as long as you don’t do anything to weaken it. At least from the Ruby user’s (the application programmer) point of view. As Ola pointed out on #jruby, “but manipulating it can’t crash the underlying implementation (in a perfect world, of course)”. This means that while the following adds apparent weak typing, it doesn’t actually corrupt the Ruby runtime to remove the strong typing protections it has built in.
Adam Glasgall also on #jruby pointed out that he thinks of strong typing as “every value has a type, and that type doesn’t change”. There wasn’t consensus about the latter phrase (it is also a little ambiguous), and even so it doesn’t seem to truly apply to Ruby.
Operations on incompatible types
x = 5
y = "42"
x + y # => TypeError: String can't be coerced into Fixnum
y + x # => TypeError: can't convert Fixnum into String
Open classes and operator overloading weaken this, though.
class String
alias old_add +
def + (other)
begin
self.old_add other
rescue TypeError
self.old_add other.to_s
end
end
end
y + x # => "425"
With some appropriate patching of the usual types, you could have the implicit Perl-like coercion in Ruby. Do you reallly want it? Perhaps not.
The important thing is that unless you do something explicit to weaken it, the Ruby runtime will strongly enforce the checking of operations on incompatible types.
Unknown messages
x.foobar # => NoMethodError: undefined method `foobar' for 5:Fixnum
Again, open classes help weaken this, as does the method_missing method.
class Fixnum
def method_missing (methId)
end
end
x.foobar # => nil
So, as long as you have a special method_missing method defined, you can weaken the rejection of unknown messages too. You could have an empty method_missing on Object and weaken this feature too.
The important thing is that unless you do something explicit to weaken it, the Ruby runtime will strongly enforce the checking of acceptable messages for a type.