A quick summary of some of the new features of Ruby 2.0.0:
Language Changes
Keyword arguments
123456789101112131415161718192021
# Ruby 1.9:# (From action_view/helpers/text_helper.rb)defcycle(first_value,*values)options=values.extract_options!name=options.fetch(:name,'default')# ...end# Ruby 2.0:defcycle(first_value,*values,name:'default')# ...end# CAUTION: Not exactly identical, as keywords are enforced:cycle('odd','even',nme:'foo')# => ArgumentError: unknown keyword: nme# To get exact same result:defcycle(first_value,*values,name:'default',**ignore_extra)# ...end
This makes method definitions very flexible. In summary:
123456
defname({required_arguments,...}{optional_arguments,...}{*rest||additional_required_arguments...}# Did you know?{keyword_arguments:"with_defaults"...}{**rest_of_keyword_arguments}{&block_capture})
In Ruby 2.0.0, keyword arguments must have defaults, or else must be captured by **extra at the end. Next version will allow mandatory keyword arguments, e.g. def hello(optional: 'default', required:), but there are ways to do it now.
Defaults, for optional parameters or keyword arguments, can be mostly any expression, including method calls for the current object and can use previous parameters.
No magic comment is needed in case the encoding is utf-8.
1234567
# Ruby 1.9:# encoding: utf-8# ^^^ previous line was needed!puts"❤ Marc-André ❤"# Ruby 2.0:puts"❤ Marc-André ❤"
Unused variables can start with _
Did you know that Ruby can warn you about unused variables?
1234567
# Any Ruby, with warning on:ruby-w-e" def hi hello, world = 'hello, world'.split(', ') world end"# => warning: assigned but unused variable - hello
The way to avoid the warning was to use _. Now we can use any variable name starting with an underscore:
123456789101112131415
# Ruby 1.9ruby-w-e" def foo _, world = 'hello, world'.split(', ') world end"# => no warning# Ruby 2.0ruby-w-e" def hi _hello, world = 'hello, world'.split(', ') world end"# => no warning either
Core classes changes
Prepend
Module#prepend inserts a module at the beginning of the call chain. It can nicely replace alias_method_chain:
# Ruby 1.9:classRange# From active_support/core_ext/range/include_range.rb# Extends the default Range#include? to support range comparisons.definclude_with_range?(value)ifvalue.is_a?(::Range)# 1...10 includes 1..9 but it does not include 1..10.operator=exclude_end?&&!value.exclude_end??:<::<=include_without_range?(value.first)&&value.last.send(operator,last)elseinclude_without_range?(value)endendalias_method_chain:include?,:rangeendRange.ancestors# => [Range, Enumerable, Object...]# Ruby 2.0moduleIncludeRangeExt# Extends the default Range#include? to support range comparisons.definclude?(value)ifvalue.is_a?(::Range)# 1...10 includes 1..9 but it does not include 1..10.operator=exclude_end?&&!value.exclude_end??:<::<=super(value.first)&&value.last.send(operator,last)elsesuperendendendclassRangeprependIncludeRangeExtendRange.ancestors# => [IncludeRangeExt, Range, Enumerable, Object...]
Refinements [experimental]
In Ruby 1.9, if you alias_method_chain a method, the new definition takes place everywhere. In Ruby 2.0.0, you can make this kind of change just for yourself using Module#refine:
# Ruby 2.0moduleIncludeRangeExtrefineRangedo# Extends the default Range#include? to support range comparisons.definclude?(value)ifvalue.is_a?(::Range)# 1...10 includes 1..9 but it does not include 1..10.operator=exclude_end?&&!value.exclude_end??:<::<=super(value.first)&&value.last.send(operator,last)elsesuperendendendenddeftest_before(r)r.include?(2..3)end(1..4).include?(2..3)# => false (default behavior)# Now turn on the refinement:usingIncludeRangeExt(1..4).include?(2..3)# => true (refined behavior)deftest_after(r)r.include?(2..3)endtest_after(1..4)# => true (defined after using, so refined behavior)3.times.all?do(1..4).include?(2..3)end# => true (refined behavior)# But refined version happens only for calls defined after the using:test_before(1..4)# => false (defined before, not affected)require'some_other_file'# => not affected, will use the default behavior# Note:(1..4).send:include?,2..3# => false (for now, send ignores refinements)
Full spec is here and is subject to change in later versions. More in-depth discussion here
Lazy enumerators
An Enumerable can be turned into a lazy one with the new Enumerable#lazy method:
1234567891011121314151617
# Ruby 2.0:lines=File.foreach('a_very_large_file').lazy# so we only read the necessary parts!.select{|line|line.length<10}.map(&:chomp).each_slice(3).map{|lines|lines.join(';').downcase}.take_while{|line|line.length>20}# => Lazy enumerator, nothing executed yetlines.first(3)# => Reads the file until it returns 3 elements# or until an element of length <= 20 is# returned (because of the take_while)# To consume the enumerable:lines.to_a# or...lines.force# => Reads the file and returns an arraylines.each{|elem|putselem}# => Reads the file and prints the resulting elements
Note that lazy will often be slower than a non lazy version. It should be used only when it really makes sense, not just to avoid building an intermediary array.
1234567
require'fruity'r=1..100comparedolazy{r.lazy.map(&:to_s).each_cons(2).map(&:join).to_a}direct{r.map(&:to_s).each_cons(2).map(&:join)}end# => direct is faster than lazy by 2x ± 0.1
Lazy size
Enumerator#size can be called to get the size of the enumerator without consuming it (if available).
When creating enumerators, either with to_enum, Enumerator::New, or Enumerator::Lazy::New it is possible to define a size too:
12345678910111213141516171819202122232425
# Ruby 2.0:fib=Enumerator.new(Float::INFINITY)do|y|a=b=1loopdoy<<aa,b=b,b+aendendstill_lazy=fib.lazy.take(1_000_000).drop(42)still_lazy.size# => 1_000_000 - 42moduleEnumerabledefskip(every)unlessblock_given?returnto_enum(:skip,every){size&&(size+every)/(every+1)}endeach_slice(every+1)do|first,*ignore|yieldfirstendendend(1..10).skip(3).to_a# => [1, 5, 9](1..10).skip(3).size# => 3, without executing the loop
Additional details and examples in the doc of to_enum
__dir__
Although require_relative makes the use of File.dirname(__FILE__) much less frequent, we can now use __dir__
1234567891011
# Ruby 1.8:requireFile.dirname(__FILE__)+"/lib"File.read(File.dirname(__FILE__)+"/.Gemfile")# Ruby 1.9:require_relative'lib'File.read(File.dirname(__FILE__)+'/.config')# Ruby 2.0require_relative'lib'# no need to use __dir__ for this!File.read(__dir__+'/.config')
# Ruby 2.0:ary=[0,4,7,10,12]ary.bsearch{|x|x>=6}#=> 7ary.bsearch{|x|x>=100}#=> nil# Also on ranges, including ranges of floats:(Math::PI*6..Math::PI*6.5).bsearch{|f|Math.cos(f)<=0.5}# => Math::PI * (6+1/3.0)
to_h
There is now an official way to convert a class to a Hash, using to_h:
It used to be tricky to know which method just called. It wasn’t very efficient either, since the whole backtrace had to be returned. Each frame was a string that needed to be first computed by Ruby and probably parsed afterwards.
Enters caller_locations that returns the information in an object fashion and with a better api that can limit the number of frames requested.
How much faster is it?
A simple test gives me 45x speedup for a short stacktrace, and 100x for a stacktrace of 100 entries!
The extra information like the file path, line number are still accessible; instead of asking for label, ask for path or lineno.
Optimizations
It’s difficult to show most optimizations by code, but some nice optimizations made it in Ruby 2.0.0. In particular, the GC was optimized to make forking much faster.
One optimization we can demonstrate was to make many floats immediates on 64-bit systems. This avoids creating new objects in many cases:
1234567
# Ruby 1.94.2.object_id==4.2.object_id# => false# Ruby 2.0warn"Optimization only on 64 bit systems"unless42.size*8==644.2.object_id==4.2.object_id# => true (4.2 is immediate)4.2e100.object_id==4.2e100.object_id# => false (4.2e100 isn't)
install with rvm: rvm get head && rvm install 2.0.0 (note that rvm get stable is not sufficient!)
install with rbenv: rbenv install 2.0.0-p0 (maybe, see comment by Artur Hebda)
other installation: See the ruby-lang.org instructions
For those who can’t upgrade yet, you can still have some of the fun with my backports gem. It makes lazy, bsearch and a couple more available for any version of Ruby. The complete list is in the readme.