Ruby 2.0 - the 20th birthday present
by Tim Keller and Christian Albrecht
24 February 2013 marked the release of version 2.0 of the Ruby programming language, as well as the 20th anniversary of its inception.
The first Ruby check-in occurred on 24 February 1993, and with it, Ruby creator Yukihiro "Matz" Matsumoto set in motion the success story of a dynamically typed programming language that would gain global popularity, particularly through the Ruby on Rails web framework.
The development of Ruby 2.0 has consistently progressed from the more experimental series 1.9 versions. On their way from Ruby 1.9 to the new version, the developers fixed 800 bugs and added over 320 minor and major features. Among the new features that will have the greatest impact on Ruby programmers are
- refinements
- Enumerator#Lazy
- keyword arguments
The examples in this article were tested with the 2.0.0dev (2013-02-08 trunk 39161) version of Ruby.
Improved patchwork
At the RubyConf 2010 conference, Shugo Maeda first introduced the concept of what are called "refinements" in Ruby. Similar concepts are known as "selector namespaces" or "classboxes" in other programming languages. The basic idea behind refinements is to make "monkey patching" safer in the sense that class changes of Ruby core classes, for example, are only effective in specific contexts, but not on a "global" scale. Developers can still make class changes at runtime, but refinements reduce the risk that class extensions for different libraries have a negative impact on each other. This problem is certainly encountered in classic monkey patching, but it particularly affects Rails because many constructs are built using the monkey patching technique in this context.
To create a refinement, the refine method must be used within a module. In this case, refine is not a Ruby keyword. The method signature looks like this:
Module#refine(class, &block)
The method expects the class that is to be extended and a block. In the current development state, modules can't be instantiated. The actual extensions are defined in the block. The specification stipulates that developers may use multiple refine statements within a module.
#my_active_support.rb
module MyActiveSupport
refine String do
def blank?
self !~ /[^[:space:]]/
end
end
refine NilClass do
def blank?
true
end
end
refine FalseClass do
def blank?
true
end
end
refine TrueClass do
def blank?
false
end
end
end
To use them, the implemented refinements must be activated by calling using, which can be seen as a soft keyword.
#main.rb
require 'my_active_support.rb'
using MyActiveSupport
' '.blank?
#=> true
'Hello'.blank?
#=> false
nil.blank?
#=> true
In the example above, the refinements of MyActiveSupport are valid from the point where using is called to the end of the main.rb file. We can, therefore, say that refinements have a kind of lexical scope. The following example is designed to further clarify this idea.
#main.rb
module MyActiveSupport
refine Array do
def sum
inject(:+)
end
end
end
def call_sum(ary)
ary.sum
end
using MyActiveSupport
ary = [5,5]
p ary.sum
#=> 10
p call_sum(ary)
#=> undefined method 'sum'
This example demonstrates the effects of a method call outside of an applicable refinement area. For example, the extended sum array method is unavailable because the call_sum method is defined before using. This will trigger a runtime error later.
Why this feature is classified as experimental even though its initial behaviour appears to be consistent was explained to some degree by "Matz" himself: "Since there still remains undefined corner case behaviour in refinements, and the time is running out, I decided not to introduce full refinement for Ruby 2.0". The developer's explanation refers to issues such as performance or the currently unclear refinement inheritance question. On his blog, JRuby developer Charles Nutter has taken a critical look at a slightly older refinement specification. What form refinements will take in future versions of Ruby remains to be seen, and developers should consider carefully whether to use this feature at present.
Next: Enumerator#Lazy & keyword arguments