Skip to content
Print

Adding Type Checking to Ruby

6
Feb
2008

What’s the first thing you think of when you consider the Ruby Language?  Dynamic types, right?  Ruby is famous (infamous?) for its extremely flexible type system, and as a so-called “scripting language”, the core of this mechanism is a lack of type checking.  This feature allows for some very concise expressions and a great deal of flexibility, but sometimes makes your code quite a bit harder to understand.  More importantly, it weakens the assurances that a certain method will actually work when passed a given value.

Several different solutions have been proposed to workaround this limitation.  The canonical technique involves intensifying tests and increasing test coverage.  Ruby has some excellent unit test frameworks (such as RSpec) which serve to ease the pain associated with this approach, but no matter how you slice it, tests are a pain.  Having to rely on tests to take the place of type checking in the code assurance process can be extremely frustrating.

Another, less common technique is to simply perform dynamic type checks within the method itself.  Like so:

def create_name(fname, lname)
  raise "fname must be a String" unless fname.kind_of? String
  raise "lname must be a String" unless lname.kind_of? String
 
  fname + " " + lname
end

This code explicitly checking the dynamic kind of the parameter values to ensure that they are of type or subtype of String.  The issues with this sample should be relatively obvious.

Primarily, it’s ugly!  This sort of repetitious, boiler-plate conditional checking is exactly the sort of thing Ruby tries to avoid.  What’s more is the added bulk of all of these repetitive checks (assuming you perform one check per-parameter per-method) because far more unwieldy than just improving the rspec test coverage.

While manually type checking may be a bad solution syntactically, it’s on the right track conceptually.  What we really want is some sort of assertion that the parameters are of a certain type, but that won’t overly bloat our existing code.  We need some sort of framework that will “weave in” (think AOP) its type assertions without getting in the way our our algorithms.

Well it turns out that someone’s already done thisEivind Eklund kindly pointed me to his type checking framework in a comment on a previous post.  The basic idea is to perform the type checking assertions, but to factor the work out into an API encapsulated by an intuitive DSL.  So rather than performing all those nasty unless statements as above, we could simply do something like this:

typesig String, String
def create_name(fname, lname)
  fname + " " + lname
end

It’s really as simple as that.  Passing the type values to the typesig method just prior to a method declaration give the cue to the Types framework to perform some extra work on each call that method.  Now we have the runtime assurances that the following code will not work (with a very intuitive error message):

create_name("Daniel", 123)

Will produce the folling output:

ArgumentError: Arg 1 is of invalid type (expected String, got Fixnum)

But the fun doesn’t stop there.  Ruby encourages the “duck typing” pattern, where algorithm developers concern themselves not with what the value is but rather what it does.  This means that the type checking really should be done based on what methods are available, not just the raw type.  It turns out that the Types framework supports this as well:

class Company
  def name
    "Blue Danube"
  end
end
 
class Person
  def name
    "Daniel Spiewak"
  end
end
 
typesig String, Type::Respond(:name)
def output(msg, value)
  puts msg + " " + value.name
end
 
c = Company.new
p = Person.new
 
output("The company name is: ", c)
output("The person is: ", p)
 
output("The programmer is: ", "a genius")    # error

Types can check not only the kind of the object but also to what methods it responds.  This is crucial to enabling its adoption into modern Ruby code bases, many of which rely heavily on this “duck typing” technique.

You can think of the Types framework just like another layer in your testing architecture.  Obviously it’s not performing any sort of static type checking (since Ruby has no compile phase).  All it’s doing is providing that extra certainty that you’re never passing something weird from somewhere in your code, something that would break your algorithm.

So what’s the catch?  Well, obviously you need to have the Types framework installed.  It’s not as easy as just typing gem install types either, since the framework actually predates Ruby Gems.  You’ll have to download the framework and then copy around the types.rb file yourself.  But this is just deployment semantics.  The more interesting issue are the limitations of the code itself.

As far as I can tell, the only restriction on the framework is that it must be used within a proper class, not in the root scope.  This means that all of my examples above would have to be enclosed in a class, rather than just copy-pasted into a .rb file and run in place.  But other than this one limitation, the framework is incredibly flexible.  I really haven’t shown you the seriously interesting stuff in terms of the API (there are more examples at the top of the types.rb file).  In many ways, Types is actually more powerful than any static type checking mechanism could be (yes, I’m even including Scala in that evaluation).

I haven’t had a chance to use Types on any serious project myself, but I can see tremendous potential, particularly for companies with large-scale Ruby/Rails deployments or even smaller projects looking for just a bit tighter code assurance.  As far as I’m concerned, there shouldn’t be a non-trivial Ruby project attempted without this lovely library, Rails or no Rails.

Comments

  1. Runtime type checking is about 10.times worse then compile time imho. Static typing with type interference is preferrable for me. Maybe Scala’s implementation isn’t best because of some compatibiliby with Java, but I think it is superior to both static Java and dynamic Ruby.

    jau Wednesday, February 6, 2008 at 2:49 am
  2. I agree that this type checking has non-zero value, but it’s nowhere near as valuable as compile time checking.

    I’m a Ruby zealot, and I think that the virtues of dynamic typing far outweigh the benefits of static type checking, but static type checks do have benefits. The problem with Runtime checks is that the problem was going to fail anyway. All you’ve bought now (in the vast majority of cases) is a prettier error message. You’re still going to fail at runtime.

    Mike Harris Wednesday, February 6, 2008 at 6:26 am
  3. I can’t help but get the impression that you’re fighting against the way Ruby is designed to work here.

    The statement you have before the method definitions looks bolted-on, it doesn’t appear natural at all. I mean, you can run your car on wood if you want, but that doesn’t mean you should do it.

    This reminds me of a guy I used to work with who would catch all of his Java exceptions and silence them, instead returning an int that corresponded with the error codes he had been using in his long years as a C programmer. His code “worked,” but he was suppressing one of the core idioms of Java, making it very hard for people used to programming in Java to maintain his code. I think you’re doing something really similar here by supressing Ruby’s duck typing.

    If you hire a developer to work on your project with all that enforced static typing, and that programmer has programmed for a long time in Ruby, I bet that programmer will hate working with your code. If I had to do a project where I and my fellow programmers all agreed that we wanted static typing, I’d probably do the project in Java rather than Ruby; Java’s a wonderful programming language in its own right and has static typing elegantly built-in.

    Norman Clarke Wednesday, February 6, 2008 at 6:46 am
  4. Actually, Scala does support duck-typing. It is talked about
    here, among other places.

    Aaron Davis Wednesday, February 6, 2008 at 7:43 am
  5. Right, everything’s still going to blow up at runtime. The reason for this framework is two fold. First, it gives you a prettier (and therefore more fixable) error message. Second, it avoids problems that can be caused by bad values proceeding up the call stack, away from their origin frame. With the errors produced by this framework, you *know* that it’s a type-related problem that needs fixing and you know that it’s in the calling method. You’ve narrowed the field tremendously and made it far easier to fix. It really is just a testing tool, nothing more nothing less.

    I would agree that some of the syntax of the framework seems a bit…odd. However, the typesig prefixing call isn’t one of them. I assume you know how access modifiers work in Ruby? Same concept.

    Compile-time type checking would be very nice, and maybe it’s something that could be accomplished using this framework and, say the JRuby compiler (or perhaps XRuby). But *that* would be going against the design of the language. Ruby is a highly dynamic, interpreted language. This gives it a great deal of power, you just have to test a lot to make sure your larger applications don’t blow up on edge cases.

    I personally think Scala’s duck typing is about the ultimate way to go. Statically checking for types which satisfy certain properties is really quite useful. Incidentally, the types framework doesn’t suppress Ruby’s duck typing (since you can use Type::Respond). Rather it adds test assertions that ensure the value passed will comply with the algorithm’s requirements, be they explicitly typed or otherwise.

    Daniel Spiewak Wednesday, February 6, 2008 at 9:37 am
  6. I don’t think compile-time type checking would be nice. There are a jillion languages that support type-checking, and if I wanted that feature, I’d use one of them.

    We use Ruby precisely because we don’t want to add typesig to every method.

    ///ark

    Mark Wilden Wednesday, February 6, 2008 at 10:17 am
  7. I was actualy using Ruby to improve coding effectiveness and learn new ways of thinking. With Scala I have the same => coding speed + Java performance with all libraries. With static typing you can acomplish enormous refactoring support, something that’s very important for mission critical large systems. For now Scala tools are weak, but at the beginning of Java it was very similar. With Eclipse advanced plugin system and well thought architecture, I think it’s possible to achieve the same for Scala. With Ruby – yes, you can get some primitive refactoring and compiler / code assist features, but it’s nothing when comparing to Java. You may say I’m one of this IDE fans who can’t live on command line. That’s not true. I have programmed in plenty languages on many platforms and I was also kind of command line zealot. Eclipse has changed my way of thinking. For some people watching Eclipse in work, used by good programmers – it also looks like magic. I can’t imagine doing same things on command line again.

    jau Wednesday, February 6, 2008 at 11:58 am
  8. @jau

    Couldn’t have said it better myself. I do use Ruby quite a bit (it’s great for complex scripting tasks), but I couldn’t imagine using it for a large project – even with this type checking library. Like it or not, statically typed rule (and will always rule) the enterprise-class application paradigm.

    Daniel Spiewak Wednesday, February 6, 2008 at 3:27 pm
  9. Refactoring has nothing to do with static typing. The very idea of refactoring comes from Smalltalk, a fully dynamic language.

    Sure, there must be some cases that just can’t be done with dynamic languages — but I bet those wouldn’t be needed in a dynamic language anyway.

    Pazu Wednesday, February 6, 2008 at 6:39 pm
  10. Smalltalk’s refactorings were/are fairly crude compared to the raw power afforded by modern Java IDEs such as Eclipse or IntelliJ. Certain Ruby IDEs (specifically Aptana RadRails and NetBeans Ruby) offer fairly advanced refactorings, however they still don’t hold a candle to what can be done in staticly typed languages. The key is in static languages, runtime paths are deterministic at compile-time. Thus, semantic analysis tools can accurately predict the behavior of arbitrary code within the context of the larger project. This capability allows transformations of arbitrary complexity to maintain internal consistency (assuming the analysis is correct). This sort of power isn’t available to dynamic languages simply because they aren’t always deterministic based on information available to the tool (e.g. ActiveRecord code completion without parsing migrations).

    Daniel Spiewak Wednesday, February 6, 2008 at 6:44 pm
  11. Why should I write Ruby code that uses type checking? If I wanted that kind of hand holding I’d go back to writing C++.

    Sorry, but adding type checking to my Ruby code is just bone-headed. After 10 years of writing C++, the lack of type checking in Ruby doesn’t worry me at all.

    anonymous Wednesday, February 6, 2008 at 8:48 pm
  12. This doesn’t provide anywhere near the power of a sophisticated static type checking system. For one, you don’t have any type information about the return values of functions. In your “duck typing” example, you know that the “value” parameter responds to the “name” method, but you have no way of knowing what type is returned by “name”. How do you even know that the result of “name” can be concatenated to a String?

    Second, even with this type checking framework, you -still- need to do extensive unit testing to prevent type errors. Unless you want your production system to throw a runtime type error and crash.

    Third, type checking is done at -every- method call? A good static type system checks types once… at compile-time. Making a type check for every method call sounds like massive overhead.

    Overall, I think, you gain little (better runtime error messages) and lose much (worse performance, extra verbosity). For writing Ruby code, I’d stick with unchecked dynamic types.

    Jorge Ortiz Friday, February 8, 2008 at 3:40 am
  13. @Pazu

    Refactoring nothing to do with static typing? Lets take the most basic refactoring there is: renaming a method. This is fully automatic in any modern Java IDE and takes only a few seconds even if the method has hundreds or even thousands of uses. And the ability to do this so easily actually gives the code a more “dynamic” property in the sense that it can changed easily (unless you have already released it to clients as API). Resistance to change is minimized and the changeability of code is incredibly important in my opinion. But in my experience, some Java programmers still aren’t fully accustomed to this power and still resist changes to code.

    Can a Ruby IDE do the same and guarantee that all the possible uses of the method, or any code that relies on that method, will be renamed correctly? Somewhy, I don’t think so, but if I’m wrong, then I raise my hat to the IDE developers. (of course, even in Java, use of the method by reflection isn’t refactorable [to my knowledge no IDE tries that], but that is rare anyway.)

    I’ve seen a Ruby user claim that renames aren’t necessary in Ruby because the code is so good from the start — I call bullshit on that, all code needs to evolve and change and no one creates perfection from the start (or at all, actually).

    Erkki Lindpere Friday, February 8, 2008 at 7:56 am
  14. Both NetBeans Ruby and Aptana RadRails offer rudimentary (by Java standards) refactorings including method/field/class rename, extract method, etc (http://r2.ifs.hsr.ch/trac/wiki/TheRefactoringList). Obviously they can’t hit invocations through mechanisms like eval() and send(), but that would fall under the “reflective” category that you wouldn’t expect to be refactorable.

    Daniel Spiewak Friday, February 8, 2008 at 8:05 am
  15. Ok, I haven’t really used Ruby beyond going through a few tutorials some time ago, and looking at scripts other people wrote, but I just downloaded Aptana RadRails and created a couple of simple classes to see how well the rename works. It could be that I’m not using Ruby “correctly”, but my simple experiment shows that it doesn’t work very well. I created three files:

    —– ‘dog.rb’
    class Dog
    def makeSound
    return “woof!”
    end
    end

    —– ‘cat.rb’
    class Cat
    def makeSound
    “meow”
    end
    end

    —– ‘animal_sounder.rb’
    require ‘dog.rb’
    require ‘cat.rb’

    class AnimalSounder
    def main
    animal = Dog.new
    puts animal.makeSound
    animal = Cat.new
    puts animal.makeSound
    end
    end

    (AnimalSounder.new).main
    —–

    Now, if I rename Cat’s makeSound or Dog’s makeSound to “makeNoise”, this isn’t picked up in the other files.

    If I move Cat into the same file as AnimalSounder, then renaming Cat.makeSound does suggest that “animal.makeSound” in AnimalSounder be renamed, in both cases (for dog and cat)! Then I can *manually* select the case where I want it to be renamed.

    Erkki Lindpere Sunday, February 10, 2008 at 5:39 am
  16. It’s very strange that this wouldn’t work right. The only thing I can think of is the fact that RDT (which is what RadRails is based on) is an incredibly buggy creation. I have a massive amount of respect for Chris and what he does, but RDT just isn’t that stable. I’d be interested to see how the experimental refactoring in DLTK (not even turned on by default IIRC) or the refactorings in NetBeans Ruby stack up. They’re all using the JRuby AST parser and roughly the same refactoring code (at least in the case of DLTK and RDT), so we should be able to see where the weakness actually is.

    Daniel Spiewak Sunday, February 10, 2008 at 12:18 pm
  17. Erkki, you are completely right: most of the refactorings we are used to with statically typed languages *cannot* be done reliably with dynamically typed languages (even Smalltalk). Take a look at this article for more details:

    http://beust.com/weblog/archives/000414.html


    Cedric

    Cedric Tuesday, February 12, 2008 at 5:56 am
  18. Jorge Ortiz sez: Making a type check for every method call sounds like massive overhead.

    It certainly is, but there’s no reason that the checks have to be done in production code. For example, it’s quite possible to move the assertions off to a separate “library”, running them only when the code is being tested, etc. I have a proof of concept for this approach at http://cfcl.com/twiki/bin/view/Projects/Spect/UC_Method_Calls

    Rich Morin Saturday, April 19, 2008 at 11:25 pm
  19. http://74.125.95.132/search?q=cache:_7SQZf5WRQQJ:www.cs.umd.edu/~jfoster/ruby.pdf+ruby+static+parameters+library&hl=en&ct=clnk&cd=2&gl=us
    lists an attempt at compile time ruby checking. That’s cool.

    Roger Tuesday, December 23, 2008 at 11:27 am
  20. As of February 2010 I’ve been using Ruby for a few years, I’ve also worked in Java, Delphi and had C++ as my favorite programming language in the past. I specially loved to do speed optimizations and play with template metaprogramming. (That is to say, I’ve had many “religions”.) I’m not sure that I have fully understood the main values of duck typing, mainly due to lack of experience with it, but as I have spent about 7 months working with PHP(5) and JavaScript, mostly JavaScript, it seems to me that in practice run-time type checks for simple types like strings, hashtables, arrays, integers, has saved me a lot of time.

    If a method has to receive a string or a hashtable, but due to a typo or my fuzziness receives something else, then it saves considerable amount of development time, if that error has been reported. On the other hand, in the “duck typing” side I actually “study the duck” more thoroughly in the debug mode. For instance, whether the hashtable/object instance has all of the required fields, whether their values are within the domain, etc. Actually, in the case of my JavaScript code, the type checks for the simple types, string, int, etc., occur only in debug mode. So, I think that the truth relies again somewhere in between. As of February 2010 I think that It’s good to have some type checks, but it should be optional and in “the appropriate place”.

    Regards,
    Martin (Vahi)

    Martin Vahi Friday, February 26, 2010 at 4:09 pm
  21. I have to underline Martin Vahi’s comment. I also have been using ruby for quite some time as well as c++, C#, Java (and i have some experience in Scala, Prolog, Scheme etc). And i also saved a lot of development and debugging time because type checking caught errors as soon as possible so i did not have to spend hours searching for the origin of the errors. I would like to see some extensions to the type checking framework for attributes:

    attr count: Integer, default: 0
    attr name: [Symbol,String]
    attr enabled: Boolean # => generates method: enabled?

    Ragmaanir Thursday, April 22, 2010 at 8:27 am
  22. I guess this type checking is not a bad stuff, and yes, it could be really slow in production environment. But it can be very good under development, when it acts like an assert in C or C++. In Rails I have very usual String Integer comparing problem, that is very annoying sometimes, because printing it out shows good result (it calls the to_s method of the Integer on debugging).

    Nucc Wednesday, January 19, 2011 at 11:55 am
  23. Daniel, while I can appreciate your point of view, I think it is misguided. I would advise against doing it except under very strict circumstances (I found it in 3 spots in my production code on one healthcare project — all having to do with message handling and checking what we received…).

    More here: http://technicaldebt.com/?p=735

    Jon Kern Wednesday, February 9, 2011 at 9:40 pm
  24. I understand the value of refactoring tools like Eclipse´s for static typed languages like Java. I use a lot of these refactorings with Java, and I think they are very helpful, but renaming methods and attributes isn´t 100% guaranteed, even in Java:

    1 – if you use JSF or Spring Webflow, you have Java code called from XHTML and XML files
    2 – if you do Criteria queries with Hibernate (not JPA 2.0 style), you write code like this:

    criteria.add(Restrictions.eq(“name”, “John”));

    Your refactoring tool won´t find your “name” attribute inside a string.

    3 – If you use Reflection, you´re pretty much in the same situation as in a dynamic language.

    I know IntelliJ refactorings are more advanced than Eclipse´s, but I don´t know if they can cover the 3 situations above (maybe the first one).

    Guilherme Garnier Friday, February 18, 2011 at 3:42 am
  25. Why do you care if the arguments coming in are a string? Surely all you care is that they respond to the + method and can be concatenated together. Why limit yourself to only supporting strings? The type is not the issue – the methods that the objects coming in support are what matters. Quack quack quack.

    I appreciate it is only some simple example code – but my point still stands. Or does it?

    Eric Monday, March 14, 2011 at 8:35 am
  26. @Eric Monday,

    The point is that you want to be told at compile time whether the arguments being passed respond to “+”, and the only way to do this is to specify a type.

    Cedric Tuesday, March 15, 2011 at 2:29 pm
  27. Cedric, while there may be some very *special* cases where you need an API call to be this rigorous, it is *not the norm* in Ruby. And for good reason, as this makes otherwise flexible Ruby turn into the bloated nightmare that are most static typed languages.

    Jon Kern Tuesday, March 15, 2011 at 4:04 pm
  28. About 5 minutes ago while walking and reflecting on 5 years of Ruby and Rails programming, it occurred to me that static type checking, which I once worried about, has somehow, mysteriously become a forgotten issue in my domain. The kinds of runtime errors that most often occur in my world would not be prevented by static type-checking in any case – like unexpected data and logic errors. Granted I’m not writing aviation software. But, the larger irony is that the overall occurrence of bugs of any variety is a fraction of what it was when I was programming regularly in C/C++. The reason could be simply that I’m writing less lines of code, and thus have less chance of making errors. Another reason is that there are really only about half a dozen ‘types’ to keep straight in Ruby and similar languages. Whereas in C++ there are technically infinitely many ‘types’.

    Nonetheless I can see where the above approach might be useful to me if one could enable it during development and then disable in production.

    Russell Balest Friday, June 3, 2011 at 8:04 am

Post a Comment

Comments are automatically formatted. Markup are either stripped or will cause large blocks of text to be eaten, depending on the phase of the moon. Code snippets should be wrapped in <pre>...</pre> tags. Indentation within pre tags will be preserved, and most instances of "<" and ">" will work without a problem.

Please note that first-time commenters are moderated, so don't panic if your comment doesn't appear immediately.

*
*