Preview of the new features in Ruby 2.0.0

By: Mat Sadler

Tags:

  • ruby

A preview version of the next major release of Ruby was announced at RubyConf by Matz this week, with some great new language features. We take a swing through some of the highlights below.

Refinements

If you create a namespaced refinement:

module NumberQuery
  refine String do
    def number?
      match(/^[1-9][0-9]+$/) ? true : false
    end
  end
end

It’s not available outside that namespace

begin
  "123".number?
rescue => e
  p e   #=> #<NoMethodError: undefined method `number?' for "123":String>
end

But it is inside!

module NumberQuery
  p "123".number?   #=> true
end

You can add it to another namespace like so:

module MyApp
  using NumberQuery

  p "123".number?   #=> true
  p "foo".number?   #=> false
end

Keyword arguments

def wrap(string, before: "<", after: ">")
  "#{before}#{string}#{after}" # no need to retrieve options from a hash
end

# optional
p wrap("foo")                                  #=> "<foo>"
# one or the other
p wrap("foo", before: "#<")                    #=> "#<foo>"
p wrap("foo", after: "]")                      #=> "<foo]"
# order not important
p wrap("foo", after: "]", before: "[")         #=> "[foo]"

# double splat to capture all keyword arguments, or use as hash as keyword
# arguments
def capture(**opts)
  opts
end
p capture(foo: "bar")                          #=> {:foo=>"bar"}

# keys must be symbols
opts = {:before => "(", :after => ")"}
p wrap("foo", **opts)                          #=> "(foo)"

# the old hash style syantax is still accepted for keyword arguments
p wrap("foo", :before => "{", :after => "}")   #=> "{foo}"

Enumerator#lazy

Making an enumerable lazy makes it possible to enumerate infinite collections

require "timeout"
begin
  timeout(1) {[1,2,3].cycle.map {|x| x * 10}}
rescue => e
  p e   #=> #<Timeout::Error: execution expired>
end

p [1,2,3].lazy.cycle.map {|x| x * 10}.take(5).to_a   #=> [10, 20, 30, 10, 20]

A lazy enumerable will evaluate the entire chain for each element at a time, rather than all elements at each stage of the chain, so the following will output at 1 second intervals. Without #lazy all output would come after 3 seconds

class Foo
  include Enumerable
  def each
    sleep 1
    yield 1
    sleep 1
    yield 2
    sleep 1
    yield 3
  end
end

Foo.new.lazy.map {|x| x * 10}.each {|x| p x}

You would think that as the collection is only iterated once #lazy might speed things up, unfortunatly this generally isn’t the case

Module#prepend

module A
  def foo
    "A"
  end
end

In a regular module include, the method in the class overrides the module (module method is available as super)

class B
  include A

  def foo
    "B"
  end
end

p B.new.foo   #=> "B"

With prepend the module method overides that in the class (in the case the method in the class is available as super)

class C
  prepend A

  def foo
    "B"
  end
end

p C.new.foo   #=> "A"

Converting convention to Hash: #to_h

p({:foo => 1}.to_h)   #=> {:foo=>1}
Baz = Struct.new(:foo)
baz = Baz.new(1)
p baz.to_h            #=> {:foo=>1}

So instead of writing something overly strict like:

def foo(opts)
  raise ArgumentError, "opts must be a Hash" unless opts.is_a?(Hash)
  # do stuff with opts
end

We can go with the more versatile:

def foo(options)
  if options.respond_to?(:to_h)
    opts = options.to_h
  else
    raise TypeError, "can't convert #{options.inspect} into Hash"
  end
  # do stuff with opts
end

%i: a literal for symbol array

p %i{hurray huzzah whoop}   #=> [:hurray, :huzzah, :whoop]

Regular expression engine is changed to Onigmo

This is a fork of the Oniguruma regexp engine used by 1.9, with a few more features. More details here. The new features seem Perl-inspired, with a good reference available here

(?(cond)yes|no)

If cond is matched, then match against yes, if cond is false match against no. cond references a match either by group number or name, or is a look-ahead/behind.

This example only matches a trailing capital if there is a leading capital:

regexp = /^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/

regexp =~ "foo"   #=> 0
regexp =~ "foO"   #=> nil
regexp =~ "FoO"   #=> 0

About the Author

Mat Sadler

blog comments powered by Disqus