Seven Languages, Seven Weeks – Ruby

I just finished the first stop on my seven language tour. Ruby was in some ways an easy start, although not as easy as I would have thought. While Ruby is enough like Python to seem familiar, I was still surprised by some of the differences I found.

The author of the book, Bruce Tate, is a Rubyist as I understand it, and he was definitely pushing metaprogramming as one of Ruby’s cool features. And in that department (and in others) Ruby definitely has some cool tricks.

After some easy build-ups, including arrays and hashes (pretty much equivalent to lists and dictionaries in Python), a sort of slick implementation of a Roman numeral class, and using inject to cumulatively operate on all of the elements of an array, the final problem to be solved in Ruby is creating a module which creates a class to read a CSV file. The file is opened automatically when an object of the class is instantiated, assuming a filename derived from the class name. That is, if the class is ‘ActAsCsv’, it will open a file ‘actascsv.txt’.

The first line of the CSV file is assumed to be a header containing the names of the fields, and one is supposed to create a CsvRow class which supplies accessor methods corresponding to each field. So if the first line contains “one, two, three”, you should be able to access the third field of any row with ‘row.three’.

A few things in this exercise left me feeling uncomfortable. First of all, the automatic opening of a file based on the class name, struck me more a parlor trick to illustrate Ruby’s capabilities for introspection, than a practical strategy. While I can do the same thing in Python, I can’t think of a single instance, even in simple scripts, where I would be happy linking all instances of a class to a single file, or even constraining what file is opened. I try to write my classes to handle arbitrary file names instead. I may be making too much of this, but as a teacher I’ve come to really dislike teaching examples which make you say “huh?” right at the beginning.

I’ve always advocated to my programming students that they write programs in such a way that runtime errors are obvious. If an error matters, it should be as obvious as possible, so that you can find and fix it. so I was surprised and a little uneasy to discover that Ruby seems to be taking the opposite approach – if a field or method or array slot is missing, just return nil and carry on. That’s right, no index out of bounds exceptions… ever.

In fact, Ruby takes things one step further. Objects have a overridable method ‘method_missing’ which gets called whenever you try to access a method that the class doesn’t actually have. The default version raises a NoMethodError exception, but once you override it, the behavior is up to you. This is powerful – if you construct your method correctly you can do some interesting things, including dynamically supplying field accessors for rows in CSV file. OTOH, if you don’t construct it right, if you don’t remember to raise a NoMethodError for the truly unexpected values, you run the risk of masking important information.

In my solution, the contents of the row are a hash in the CsvRow, and the method_missing implementation of the accessors is really just syntactic sugar hiding a key, value hash lookup. Add to that the fact that Ruby returns a nil rather than raising an exception if a key isn’t found, and my code can’t tell the difference between an empty field and a missing one. I freely admit my solution may not be the “Ruby way”, but I would argue it’s at least plausible.

So while I’m glad I had the chance to play with a bit of Ruby, and I’m still liking the books approach, I end up with two issues, one regarding Ruby and the other with the example problem. The first is what I’ve just mentioned – I was guided into using Ruby in a way that I think conceals potential bugs, and Ruby was complicit in that.

The second issue is with the exercise. While I’ll certainly concede that Ruby has some syntactic flexibility that Python doesn’t, this problem doesn’t really present a convincing use case. I don’t intend to make my reading of this book into just a ‘so what, I can do that in Python’ response, but in this case it does seem to me that a Pythonic version takes care of the problem more efficiently. If I’m OK with using dictionary access rather than accessor methods – saying ‘row["one"]‘ rather than ‘row.one’ – this example is a non-problem in Python, thanks to the csv module in the standard library.

[Edit: Yes, I know that you can override __getattr__ in Python to provide the same functionality. See the comments for suggested versions. My point was that Ruby's method_missing approach was what made me uneasy. And it's more inviting - in Ruby you would override the method_missing method of a class derived from hash, and nothing else. In Python, I would argue, the surgery is a bit more serious.]

About these ads

11 Responses to Seven Languages, Seven Weeks – Ruby

  1. Benjamin says:

    You can even easily mimic the Ruby functionality you mention in the last paragraph in Python.

    The first place attribute lookups on an instance look is in the instance’s __dict__ attribute.

    class X:
    def __init__(self):
    self.__dict__ = {‘one’:1, ‘two’:2}

    x = X()
    x.one # returns 1

  2. Tim says:

    As a “Rubyist” of five years or so, I just made the switch to Python myself. My reasons?

    1. Speed … Ruby may be “fast enough” for many, but after years of working with it I grew frustrated with waiting. Python seems to be an order of magnitude or two faster.

    2. Language Maturity … I can’t fault Ruby for being a young language, but it seems to me that the core development team seems to be lacking a clear vision for the future of the language and their project management and release practices could use some serious attention. All in all I was left with the feeling that Ruby is more of a “language playground” than a serious tool I could count on from release to release.

    3. Library Maturity … Many of Ruby’s standard libraries seem out of date and, while there are a few stellar third party libs, many I found seemed incomplete. Add to that that there just aren’t Ruby libs for many things and one is faced with having to write significant libs themselves just to write the code they need to write in the first place.

    4. No Magic Code … As impressed as I was with the seemingly magical things Ruby can do, I found it very hard to justify their use in serious projects given the fact that such code is often very hard for all but the magician to understand. Python may lack some of the syntactic sugar and magic, but I feel that just leads to code that is easier to understand.

    5. Platform Support … I am on Linux but I routinely write code for consumption on Windows and work with developers who use Windows themselves. Ruby on Windows is generally much harder to work with and make work than Python seems to be. Another bonus for me is that my platform of choice, Ubuntu, utilizes Python extensively.

    Don’t get me wrong, this is not meant as a “bash Ruby” comment… If it were not for Ruby I’d probably still be deeply entrenched in Java – Ruby was powerful and flexible enough for me to make the switch to a much more dynamic way of developing.

    That said, Python just seems to fit my needs much better at this stage of things and it allows me to focus more on the problems I’m trying to solve rather than the tools I’m using to do so.

  3. Dave Kirby says:

    You can of course do the same row.value trick in Python by implementing __getattr__ on the Row class and looking up the attribute name in the dictionary.

    • Vern Ceder says:

      Of course, you can get the same effect by implementing your own __getattr__ on a Row class (perhaps one derived from dict). My point, which I didn’t make clearly enough perhaps, is that from my perspective and given the csv module there is no real reason to want to.

      And I now think I didn’t make it clear that I also don’t mean to bash Ruby – just that some of its tradeoffs and ways of thinking leave me uneasy. Of course, that’s one of the advantages of looking at various languages – in addition to seeing new possibilities, you can appreciate more clearly what you like about the ones you know.

      • Tim says:

        Ruby, too, has a (sloooow, buggy) csv parser in the standard lib but most choose to use a really nice third-party lib (FasterCSV) instead… In any case, the choice of CSV parsing in the book does seem a bit odd.

        I certainly agree that there are many benefits of looking at different languages… Sometimes seeing how something is done in an unfamiliar languages is inspiring and sometimes it is enough to warn you to stay away :-D

        I recently discovered the Rosetta Code wiki (http://rosettacode.org) which takes different tasks and compares solutions across many different languages, even going so far as to include those that don’t really seem all that suited to the problem domain :-) Very interesting stuff!

  4. mark says:

    In fact in python you can also use row.one if you override the __getattr__ method.

    for example (don’t know if formatting will get messed up though)

    def __getattr__(self, name):
    if name not in self.col_names:
    raise AttributeError(“Row object has no attribute: ‘%s’”% name)
    return self.row_data[name]

  5. craig ferry says:

    First of all what a great article, it has made me think so much more deeply about the first chapter of the book than I had originally.

    Your comments about out of bound errors on arrays, I thought was interesting as I have never seen it as problematic before. Arrays tend to be iterated through most of the time and the length of the array could be checked for the few times I needed to raise an out of bounds error. Maybe this is more of a problem of me thinking inside the constraints of ruby because I program in ruby.

    A lot of the comments such as introspection and opening a file name based on the name of the class as being parlor tricks I think lean on the harsh side. I would suggest that this is probably an extension of the ruby philosophy of convention over configuration however it would be very easy to override this behaviour if you didn’t find it useful.
    This brings me to my main point. The major points of what makes ruby’s core syntax and what makes it different is covered in just 32 pages. I think a lot of what is shown to demonstrate ruby’s strengths in a short space of text do not imply good programming practice and would not necessarily be done in practice.
    I have never felt the need to implement the method_missing method myself in the code that I write. However, I take advantage of it simply by the ruby frameworks that I use.

    Having said this the appropriateness of practices such as monkey patching is a hotly debated issue. As the language matures people seem to be reaching common ground on what is appropriate. The merging of the rails and merb code base seem to be indicative of this.

    Anyway great article and look forward to hear your comments on io (If you thought what you could do with ruby was dangerous, I think io might well give you a heart attack) :-)

  6. Vern Ceder says:

    Thanks for the kind words and thanks also for pointing out the spots where you think I went a tad too far – you’ve got some good points there.

    I very much had the feeling that we might not be getting exactly the best practice for Ruby, and I certainly appreciate that its features can be used supremely well by those who know them.

    However, I’m really against using examples in teaching that go against good standard practice – bad habits are easy enough to acquire on our own, without any extra help. In Python we talk about “Pythonic” code… I wonder what is the equivalent in the Ruby community?

    I teach high school kids programming as part of my job, and that probably shows. I never have to worry about them being willing to try something different, cool or risky, and most of my rants tend more towards “how not to shoot yourself in the foot”. :)

    One conclusion that I didn’t mention (and probably should have) is that I can completely understand how someone who really is comfortable in Ruby would love it, just as we Pythonistas love coding in Python. We’d darned well better get along, because both languages are too good for one side to win over the other. ;)

    As to Io, I compiled it last night, so I’ll start on it tonight, with tranquilizers and nitroglycerin pills handy. ;)

    Again, thanks for the comments.

  7. craig ferry says:

    I wholeheartedly agree with your comments. Your third paragraph really rings out to me and cuts to the quick in what separates languages apart.
    When I look at ruby and python I think there is more commonality than features that set them apart, which begs the question what makes them different. I think you nail this when you talk about python code being pythonic. To me as an outsider pythonic is about clarity over conciseness. Ruby’s main mantras are convention over configuration and DRY (don’t repeat yourself) which could conflict with the idea of things being pythonic.
    Also I am sure it is possible to write ruby code that is pythonic and python code that echoes ruby’s mantras.
    It’s funny that we tend to cling to our favourite languages. I find myself in that position at the moment. The google appengine has my interest. I have found myself looking at convoluted solutions such as jruby on rails using the java appengine framework even though this suffers a big performance hit, rather than just getting off my backside and writing it in django.
    Anyway your post made me think about how important choice of language is and I have blogged about it at http://www.techsolutions.me.uk/2010/04/03/does-language-really-matter/ where I have linked to your blog post here. I hope you don’t mind

  8. One problem I always have when trying to learn a new language is thinking in terms of the new language and not the previous one. For example, it took me ages to think like a Python developer after years of Java development.

    The same thing is what is currently stopping me from learning Ruby. Until I can learn to think and see Ruby as a different language, I will always struggle with it.

    Nice article BTW

  9. pedrogonzalezma says:

    The problem with Ruby is that it is not suitable for international applications because Ruby lacks Unicode support, unlike Python that has really good Unicode support

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 131 other followers

%d bloggers like this: