Matthew Nielsen SLC.rb - April 23, 2019
The Japanese calendar will soon be moving from the Heisei era ("平成" or "㍻") to the Reiwa Era ("令和"). Ruby 2.6.3 adds support for Unicode 12.1 which adds a single-width character point for the new era.
Reference: https://bugs.ruby-lang.org/issues/15742
It also adds support for the new era in core Date library:
require 'date'
# => true
Date.new(2019, 5, 1).jisx0301
# => "H31.05.01" # < 2.6.3
# => "R01.05.01" # >= 2.6.3
Becha' didn't even know that calendar was built in to Ruby? But it makes sense in hindsight.
Reference: https://bugs.ruby-lang.org/issues/15195.
News: https://github.com/ruby/ruby/blob/trunk/NEWS
Ruby 2.7 is currently under active development. If you would like to try it
out, you will need to compile it from the current trunk
or use a tool such as rvm
to install it:
$ rvm install ruby-head
# ... about 8 minutes on my Macbook Pro 2017 ..
$ ruby -v
ruby-head is 2.7.0dev0 (2019-04-24)
Previously, to get a reference to a method on an object, you would use:
"Ruby".method(:length)
# => #<Method: String#length>
With this feature, you can use the colon (:
) directly:
"Ruby".:length
# => #<Method: String#length>
What do you need the method reference for anyway? For things like this:
x = 'zzz'
f = x.:length
f.call
# => 3
x.succ!
# => 'aaaa'
f.call
# => 4
Reference Issue: https://bugs.ruby-lang.org/issues/15658
def foo(opt=nil, **kw)
p opt, kw
end
foo("str" => 42, :sym => 42)
# {"str"=>42}
# {:sym=>42}
# => [{"str"=>42}, {:sym=>42}]
Previously, this would fail with the error
non-symbol key in keyword arguments: "str" (ArgumentError)
A beginless range is experimentally introduced. It might not be as useful as an endless range, but would be good for DSL purpose.
[..3]
# => [nil..3] # vs [0..1]
%i[foo bar baz][..-2] # identical to `ary[0..-2]`
# => [:foo, :bar]
References: https://medium.com/@baweaver/ruby-2-7-pattern-matching-first-impressions-cdb93c6246e6
The primary change is the new in
keyword, which replaces when
.
case 0
in 0 # equivalent to `when 0`
true
else
false
end
# => true
But we can add more complex pattern matching now:
case 0
in 0 | 1
true
end
# => true
Using "captured variables" (similar to block variables) we can also know what
matched, much like $1
in String#match
:
case 'foo'
in bar
bar
end
# => 'foo'
case ['foo','bar']
in bar,baz
puts bar
puts baz
end
# foo
# bar
# => nil
Conditionals can also be used now:
case 0
in a if a == 0
true
end
# => true
Finally, important, if you are using in
and there is no match and no else
clause, a NoMatchingPatternError
will be raise:
case 0 in 1 then true end
# => NoMatchingPatternError (0)
Reference: https://medium.com/@baweaver/ruby-2-7-numbered-parameters-3f5c06a55fe4
Instead of using captured/block variable names, you can also reference them by
their position using the @
character followed by a number:
[1, 2, 3].map {
@1 + 3 # `@1` is the first parameter of the block
}
# => [4, 5, 6]
..however, bizzarely, they are numbered beginning from 1 instead of 0
as you would expect. Perhaps 0
is reserved to be a reference to the function
itself?
Another caveat, they are currently only valid in blocks. Using this anywhere else will raise:
SyntaxError: numbered parameter outside block
Reference: https://medium.com/@baweaver/ruby-2-7-enumerable-tally-a706a5fb11ea
#tally
is a more efficient way to count the number of items a unique item
appears in a set. For example, this Ruby 2.6 code:
%i[foo bar foo].reduce(Hash.new(0)) { |accumulator, value|
accumulator[value] += 1
accumulator
}
# => {:foo=>2, :bar=>1}
..can be done like this with #tally
:
%i[foo bar foo].tally
# => {:foo=>2, :bar=>1}
Why Use It? If you’ve been using Ruby, chances are you’ve used code something like one of these lines to do the same thing tally is doing above:
list.group_by { |v| v.something }.transform_values(&:size)
list.group_by { |v| v.something }.map { |k, vs| [k, vs.size] }.to_h
list.group_by { |v| v.something }.to_h { |k, vs| [k, vs.size] }
list.each_with_object(Hash.new(0)) { |v, h| h[v.something] += 1 }
Reference: https://bugs.ruby-lang.org/issues/15618
This feature allows you to directly pass the output of one interator in to another method, in order to delegate the actual iteration duties.
enum = Enumerator.new { |y|
Dir.glob("*.md") { |file|
File.open(file) { |f| f.each_line(&y) }
}
}
# => #<Enumerator: #<Enumerator::Generator:0x00007ff346a3f868>:each>
enum.next
# => "What's new in Ruby >= 2.6.4, 2.7, and 3.0\n"
enum.next
# => "-----------------------------------------\n"
Watch the RubyHack 2019 video!