【一人 bugs.ruby Advent Calendar 2020】[Feature #17292] Hash Shorthand / Punning【19日目】

一人 bugs.ruby Advent Calendar 2020 19日目の記事になります。

[Feature #17292] Hash Shorthand / Punning

これは { a:, b:, c: }{ a: a, b: b, c: c } のように展開しようという提案です。

a = 1
b = 2
c = 3
{ a:, b:, c: } # => { a: 1, b: 2, c: 3 }

この手の提案は昔から多くてわたしもいくつか似たような機能の提案しました。

今思うとよくこんな提案しようと思ったなあ、と懐かしくなりますが…。
多分一番最古のチケットな以下のチケットかな? 関連するチケット を見てもらえればどれだけ同じような提案がされていたのかが分かると思います(そして全部 Reject されています…。

そんな Reject されまくっているチケットですが今回チケットを建てたモチベーションとしては { a:, b:, c: } という記述はパターンマッチでも書かれる記述になったのでどうだろうか、と書かれています。

case { x: 1, y: 2 }
in { x:, y: }
  { x:, y: y + 1} # new
else
  # ...
end

この書き方は右代入(や1行 in)でも書かれているのでこのあたりが一般化すれば承認されそうな感じはしますねえ。

【Ruby 3.0 Advent Calendar 2020】銀座Rails#28 で Ruby 3.0 の話をした【19日目】

Ruby 3.0 Advent Calendar 2020 19日目の記事になります。

銀座Rails#28Ruby 3.0 の話をした

ちょうど preview1 がリリースされた頃の 銀座Rails#26 でも Ruby 3.0 の話をしたんですが、その時からかなり仕様が変わった機能などもあったのでその訂正をする意味合いで再度 Ruby 3.0 の話をしました。
基本的には前回話した内容とはあんまり重複しないようにしつつ、前回話せなかった内容や変更点、非互換な部分などなどを重点的に話しました。
余談ですが今回は直前までスライドを書いてたのでぶっつけ本番だったのですがなんとか時間通り〆る事がよくてよかったよかった…。
時間足りないと思って前半は架け橋で喋ったんですが最後の方は若干時間があまり気味だったので時間配分難しいですねえ。
Scheduler に関してはかなり興味があるのでまた別の機会で色々と話してみたいですねえ。

12月25日にリリースされる Ruby 3.0 に備えよう!

来週の25日に無事リリースされるといいですねえ。

【一人 bugs.ruby Advent Calendar 2020】[Bug #16852] Refining Enumerable fails with ruby 2.7【18日目】

一人 bugs.ruby Advent Calendar 2020 18日目の記事になります。

[Bug #16852] Refining Enumerable fails with ruby 2.7

Ruby 2.7 系の Refinements のバグ報告チケットになります。
元々は以下のように RSpec での再現コードで報告されていた。

# file: refinement_bug.rb
module RefinementBug
  refine Enumerable do
    def refined_method
      puts "Called #refined_method"
    end
  end
end
# file: spec/spec_helper.rb
require_relative "../refinement_bug"
# file: spec/test.rb

require_relative "../refinement_bug"

using RefinementBug

puts "Using Ruby #{RUBY_VERSION}"
[].refined_method

上記のファイルがある状態で rspec を実行すると以下のようなエラーが発生していた。

~/minimal_refinement_failure> rspec spec/test.rb
Using Ruby 2.7.1

An error occurred while loading ./spec/test.rb.
Failure/Error: [].refined_method

NoMethodError:
  undefined method `refined_method' for []:Array
# ./spec/test.rb:8:in `<top (required)>'
No examples found.

Finished in 0.00003 seconds (files took 0.12287 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples

spec/test.rbusing RefinementBug しているのに #refined_method が見つからないってエラーになっています。
これ、わたしが最小構成調べて以下のようなコードで再現するのを見つけました。

module RefinementBug
  refine Enumerable do
    def refined_method
      puts "Called #refined_method"
    end
  end
end

# Error if `require "stringio"`
require "stringio"

using RefinementBug

puts "Using Ruby #{RUBY_VERSION}"

# error: undefined method `refined_method' for []:Array (NoMethodError)
[].refined_method

で、これがなんでエラーになっているのかというと stringioinclude Enumerable を行っているんですが『 Enumerablerefine で拡張したあとに include Enumerable された場合に RefinementBug が反映されなくなる』という現象でした。
更に最小構成なコードは以下になります。

module M; end
class X
  include M
end

module RefinementBug
  refine M do
    def refined_method
      puts "Called #refined_method"
    end
  end
end

class Y
  # Error if include M
  include M
end

using RefinementBug

puts "Using Ruby #{RUBY_VERSION}"

# error: undefined method `refined_method' for #<X:0x000055d4e22468c0> (NoMethodError)
X.new.refined_method

Refinements で M を拡張したあとに include M をするとなぜか以前の挙動がぶっ壊れるというやつです。
これ、何でバグってるのか全然わからないんですが現在は修正済みになっています。

【一人 bugs.ruby Advent Calendar 2020】[Feature #17371] Reintroduce `expr in pat`【17日目】

一人 bugs.ruby Advent Calendar 2020 17日目の記事になります。

[Feature #17371] Reintroduce expr in pat

これは1行 in を再度入れ直そう、という旨のチケットです。
これ、ちょっとコンテキストが複雑なんですが、元々 Ruby 2.7 で 1行 in という機能が入りました。
1行 in は元々真理値を返す挙動で以下のように条件分岐で利用する事ができました。

# name が String 型で age が20以下の場合にマッチする
if user in { name: String, age: (..20) }
  puts "OK"
end

しかし、その後 [Feature #16355] Raise NoMatchingPatternError when expr in pat doesn't match で『条件にマッチしなかった場合でも変数が参照できるのは聞けなのではないか』という話がでて、最終的にマッチしなかった場合は例外 NoMethodError を発生させるようにしました。

# こんなコードを書いてマッチしなかった場合は x が nil になる
expr in [0, x]
# ...
# (snip)
# ...
# ここで nil を参照してしまうのは危険ではないだろうか?
x.foo

これが Ruby 2.7 時点での挙動になります。
その後 Ruby 3.0 の開発中にこの 1行 in=> へと置き換わりました。
このあたりの話は以下の記事を参照してください。

なので 1行 inRuby 3.0 では(一旦)廃止される予定でした。
しかし、『値を割り当てるのは => で行うことで回避できる』という理由で再び『真理値を返す in 』が追加されました。
これを利用すると以下のように条件分岐でパターンマッチを使うことができます。

# name が String 型で age が20以下の場合にマッチする
if user in { name: String, age: (..20) }
  puts "OK"
end
users = [
  { name: "homu", age: 14 },
  { name: "mami", age: 15 },
  { name: "mado", age: 14 },
]
# こんな感じで絞り込むことができる
pp users.select { _1 in { name: /^m/, age: (...15) } }

これは普通に欲しい機能だったので Ruby 3.0 にはいって嬉しいです。
ただし、この機能を使用すると experimental warning が出るので注意してください。

# warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
if user in { name: String, age: (..20) }
  puts "OK"
end

【一人 bugs.ruby Advent Calendar 2020】[Feature #15504] Freeze all Range object【16日目】

一人 bugs.ruby Advent Calendar 2020 16日目の記事になります。

[Feature #15504] Freeze all Range object

リテラルRange を生成すると同じ id のオブジェクトが返ってくるケースがあります。

# これは個別の id が返ってくる
p (1..3).__id__  # => 60
p (1..3).__id__  # => 80
p (1..3).__id__  # => 100

def hoge
  (1..3).__id__
end
# これは同じ id が返ってくる
p hoge    # => 120
p hoge    # => 120
p hoge    # => 120

これの影響で次のようなコードは奇妙な動作になります。

2.times{
  r = (1..3)
  p r.instance_variable_get(:@foo)
  #=> 1回目は nil が返る
  #=> 2回目は :bar が返る
  r.instance_variable_set(:@foo, :bar)
}

これは1回目の呼び出しの r.instance_variable_set(:@foo, :bar) が2回目の呼び出しでも反映されているためです。
他にも Ractor との兼ね合いもあって Range リテラルは暗黙的に freeze されるようになりました。

p (1..3).frozen?  # => true
p (1..3).frozen?  # => true
p (1..3).frozen?  # => true

def hoge
  (1..3).frozen?
end
p hoge    # => true
p hoge    # => true
p hoge    # => true
2.times{
  r = (1..3)
  p r.instance_variable_get(:@foo)
  # error: `instance_variable_set': can't modify frozen Range: 1..3 (FrozenError)
  r.instance_variable_set(:@foo, :bar)
}

これは地味に影響がありそうな気がするんですけどどうなんですかね。今の所既存のコードが壊れた、みたいな報告は見ていませんが…。
これは補足ですが frozen された Range でも次のように中の値が変わってしまうことがあるので注意する必要があります。

r = ('a'..'z').freeze
r.end.upcase!
p r # => "a".."Z"

【一人 bugs.ruby Advent Calendar 2020】[Bug #16919] IRB でマジックコメントの : の後に任意の文字を入力するとエラーが発生する【15日目】

一人 bugs.ruby Advent Calendar 2020 15日目の記事になります。

[Bug #16919] IRB でマジックコメントの : の後に任意の文字を入力するとエラーが発生する

そのまんまの意味なんですが Ruby 2.7 の irb 上で #coding:utf-8 を入力するとクラッシュするというバグ報告です。
これはコピペする場合は問題ないんですが手入力で #coding:utf-8 を入力しようとすると確定する前に #coding:u の入力時点でクラッシュします。
この問題は irb が入力毎に Ripper で入力したコードをパースしているのが原因になります。

require "ripper"

# error: unknown encoding name: u (ArgumentError)
Ripper::Lexer.new("#coding:u").scan

この不具合はすでに修正されており Ruby 2.7.2 にバックポートされているので現在は問題なく動作すると思います。

【一人 bugs.ruby Advent Calendar 2020】[Feature #12901] Anonymous functions without scope lookup overhead【14日目】

一人 bugs.ruby Advent Calendar 2020 14日目の記事になります。

[Feature #17054] Some NilClass methods are faster if implemented in Ruby

これは NilClass#to_i などを C言語じゃなくて Ruby で実装することで高速化するというチケットになります。
何を言ってるのかと思うんですがそのままの意味で C言語ではなくて Ruby のコードとして実装するとメソッドキャッシュの恩恵を受けて高速化するケースがあるという話ですね。
Ruby で実装するよりも C言語で実装するほうが早くなるという先入観があるのでこういう話は面白いですね。
RubyRuby を実装する話は以下の記事も参考になります。

他にも TrueClass のメソッドとかも Ruby で実装すると高速化するチケットがあったりして面白いです。