Skip to content

Instantly share code, notes, and snippets.

@xunker
Created April 24, 2019 16:49
Show Gist options
  • Save xunker/ebd407746ca1694f2b4b498662fa003a to your computer and use it in GitHub Desktop.
Save xunker/ebd407746ca1694f2b4b498662fa003a to your computer and use it in GitHub Desktop.
What's new in Ruby >= 2.6.3, 2.7, and 3.0 - SLC.rb - April 23, 2019

What's new in Ruby >= 2.6.3, 2.7, and 3.0

Matthew Nielsen SLC.rb - April 23, 2019

2.6.3

Support for new Japanese Era Characters

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

Support for new Japanese Era Calendar

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.

2.7

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)

Method reference operator (Experimental)

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

Non-symbol keys in keywords arguments hash

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)

Begin-less range (Experimental)

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]

Patterns and case (Experimental)

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)

Numbered Parameters (Experimental)

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

Enumerable#tally

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 }

Enumerator::Yielder#to_proc

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"

3.0

Watch the RubyHack 2019 video!

Memory

Performance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment