Sunday, January 29, 2012

Seven Languages in Seven Weeks: Ruby, Day 2

In my previous post, I went through the Day 1 Ruby problems from Seven Languages in Seven Weeks. Today, I'll share my solutions to the Day 2 problems and some more thoughts about Ruby.

Ruby, Day 2: Thoughts

I originally learned Ruby (and many other programming languages) the "hacker way": that is, I did a 10 minute syntax tutorial, browsed other peoples' code a bit, and then just started using the language, looking up missing pieces as I went. Although this is the most fun and productive way I've found to get started with a language, it can also lead to missing some of the finer points and subtleties.

For example, until the "Ruby, Day 2" chapter, I never had a full appreciation for Ruby code blocks and the yield keyword. For example, even though I frequently used "times" to do looping, I never thought deeply about how it worked:


It turns out that times is just a function (slightly obscured because Ruby doesn't require parentheses for function calls) on the Integer class that takes a code block as an argument. Times could be implemented as follows:


This style of coding allows for some powerful possibilities. For example, it is surprisingly easy to introduce a "do in a transaction" function:


Using this, I can now trivially wrap any number of statements in a transaction:


The equivalent in less expressive languages, such as Java, often involves vastly more code, implementing arbitrary interfaces, anonymous inner classes, and a lot of very hard-to-read code. For comparison, here is an example of how Java's Spring Framework recommends wrapping JDBC code in transactions:



Ruby, Day 2: Problems

The Day 2 problems are only slightly tougher than Day 1. The most fun part was coming up with a way to keep the code as concise as possible.

Print 16
Print the contents of an Array of 16 numbers, 4 numbers at a time, using just "each". Now, do the same with "each_slice" in Enumerable.


Tree
Modify the Tree class initializer (original code here) so it can accept a nested structure of Hashes. Trickiest part here was that the "collect" function can call the passed in block with either one argument that's an Array or two arguments that represent the (key, value) pair.


Grep
Write a simple grep that will print the lines and line numbers of a file having any occurrence of a phrase anywhere in that line.


Ruby vs. Java, Round 2

I couldn't resist implementing the grep code in Java to see how it compares:


It's 33 lines long. The Ruby solution was a one-liner.

Ruby, Continued


Check out more Ruby goodness on Ruby, Day 3.


4 comments:

Zoran Simic said...

I read this via Google Reader and found the post odd after a few paragraphs, like something was missing. It became clear when I came to the actual post: the code bits from Gist do not appear in Google Reader! Something was indeed missing :).

I tend to learn my languages the "hacker way" too, haven't gotten around to learning Ruby yet (but will at some point), so this is an interesting overview for me.

Can't do a one-liner for grep in python too: tried, can't make it less than 8 lines without breaking the python spirit :)

Yevgeniy Brikman said...

@Zoran: heh, yes, this post would be a bit boring without the Gists :)

Ruby has its downsides, but it's very quick and easy to learn and is so flexible and expressive that you're always wondering if there is a slightly shorter/cleaner/better way to express each and every line of code.

bakeshoppe said...
This comment has been removed by a blog administrator.
Brandon Gresham said...

With the Safari efficiency improvements in the recent OSX upgrade, I convinced myself to make the switch. Today I noticed that Safari can't display your gists.

Back to Chrome it is.

Since 2010 when I got my first mac I refused to use Safari. They finally gave me a compelling-reason to switch, and they wound up blowing it.