Preview of the new features in Ruby 2.0.0
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