【一人 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 }
この手の提案は昔から多くてわたしもいくつか似たような機能の提案しました。
- [Feature #15286] Proposal: Add Kernel.#expand(*args)
- [Feature #14973] Proposal of percent literal to expand Hash
今思うとよくこんな提案しようと思ったなあ、と懐かしくなりますが…。
多分一番最古のチケットな以下のチケットかな? 関連するチケット
を見てもらえればどれだけ同じような提案がされていたのかが分かると思います(そして全部 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#28 で Ruby 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.rb
で using 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
で、これがなんでエラーになっているのかというと stringio
で include Enumerable
を行っているんですが『 Enumerable
を refine
で拡張したあとに 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行 in
は Ruby 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言語で実装するほうが早くなるという先入観があるのでこういう話は面白いですね。
Ruby で Ruby を実装する話は以下の記事も参考になります。
他にも TrueClass
のメソッドとかも Ruby で実装すると高速化するチケットがあったりして面白いです。