Ever wished you could write code in a very expressive way like the following examples?
expiration_date = 3.years.from_now
birth_date = 32.years.ago
Now you can, in Ruby at least.
Two years ago I was a very happy Python developer that thought no other language could compete with Python for developer productivity. My Python indoctrination occurred after 7 solid years of proclaiming Java was the ultimate language that would reign supreme. Prior to my Java phase I was a C++ and Perl coder.
It was also two years ago that I got my first glimpse at Ruby. I read articles, blogs and forums between Python and Ruby heads discussing finer points of each language and how one is superior to the other.
It was at this time that Ruby's concept of reopening classes came to my attention. As a hearty Python developer at the time I came to the conclusion that Python had to be superior on this point because Python allowed class reopening through metaclasses, so the original author of the code could be explicit to allow class reopening or not. In Ruby, however, anyone could reopen your classes, write malicious code and redploy without others knowing.
Over the past two years I have realized that while Python is a beautiful programming language (not just scripting language) in its own right, my views on this point have changed considerably.
To show class reopening I will be using an example of code that is very similar to some Ruby standard library extensions found in Active Support included in Rails (Disclaimer: This is not necessarily the same underlying code - I have not checked to be honest).
Reopening Ruby classes in the interactive shell (irb)
Let us start up an irb
shell session and write the following:
irb> 3.class
=> Fixnum
irb> class Fixnum
irb> def years
irb> return self*365*24*60*60
irb> end
irb> end
=> nil
irb> 3.years
=> 94608000
irb> 3.years.class
=> Fixnum
irb> class Fixnum
irb> def ago
irb> return Time.now() - self
irb> end
irb> def from_now
irb> return Time.now() + self
irb> end
irb> end
=> nil
irb> 3.years.from_now
=> Sun Aug 09 14:23:10 CDT 2009
irb> 3.years.ago
=> Mon Aug 11 14:23:19 CDT 2003
Did you follow what I just did? I wanted to show what 3
s class is
in Ruby. To those not very familiar with Ruby, this was important to
show, since newbies might expect 3.class to be Integer
but in fact
it is of type Fixnum
.
Next we reopened the Fixnum
class for new definition. If we had
reopened it and inserted a method called floor
or ceil
we would
have overwritten the original Ruby standard library implementation of
these methods. Instead we defined a method called years
which wasn't
previously defined in Fixnum. Then we closed the class and found that
3.years
returns 94608000
. This is the number of seconds in a year.
Is that useful? Not yet, so next we reopened Fixnum
again and insert
two methods:
ago
= from_now
After reclosing the class again, we can write partial sentences in Ruby and provide an extremely expressive API for an application that deals a lot with dates and converting numbers to dates.
This couldn't be done in Python with the same ease using metaclasses,
because the builtin int
type cannot be reopened unless you redefined
the int
type from scratch in Python including the addition of your
special purpose methods in the mix.
Anyway, this might not convince all readers of the significant utility of the class reopening feature of Ruby, but I have began to love this feature, arguably too much (some may say). I have found it has significantly improved readability and condensed the code necessary to do many tasks.
After converting to Java a number of years ago from Perl and C++, I vowed not to take the Perlist's attitude toward trusting clients of your code not to bastardize it. However, I find myself accepting that if a client of my code knowingly decides to take this risk, it is on their own head they fall. I should not be bothered by this professionally or personally.
This feature of Ruby is a stepping stone to promote the development of embedded domain specific languages (eDSL), which is difficult at best to do in many other languages.
If you enjoyed this content, please consider sharing this link with a friend, following my GitHub, Twitter/X or LinkedIn accounts, or subscribing to my RSS feed.