2020/12/03 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

Delete or warn deprecated (rev.3)

  • 非推奨になっている機能を消す PR
  • まだマージはされてないんですが Ruby 3.0 までに merge されるかも?

[Feature #17355] Using same set of names in or-patterns (pattern matching with Foo(x) | Bar(x))

# * いまは以下のようなパターンマッチ構文を書くとエラーになる

case [1, 2]
# error: duplicated variable name
in [1, a] | [a, 3]
end
  • これをエラーではなくて動作させたいというチケット
  • 具体的なユースケースとしては以下のようなコードが上げられている
User = Struct.new(:email)
Admin = Struct.new(:email)
Moderator = Struct.new(:email)

def user_email(user)
#   case user
#   in User(email:) then email
#   in Admin(email:) then email
#   in Moderator(email:) then email
#   end
  # 上のコードを以下のようにかける
  case user
  in User(email:) | Admin(email:) | Moderator(email:) then email
  end
end

pp user_email(User.new("hoge@example.com"))
pp user_email(Admin.new("hoge@example.com"))
pp user_email(Moderator.new("hoge@example.com"))
  • これは普通に便利そうな予感
  • ちなみにコメントで以下のように & を使った場合の例も書かれていました
def user_email(user)
  case user
  in (User | Admin | Moderator) & { email: } then email
  end
end

[Feature #8421] add Enumerable#find_map and Enumerable#find_all_map

  • Enumerable#find_mapEnumerable#find_all_map を追加する提案
    • チケット自体はかなり前に立てられたもの
  • 最近『以下のように代替できるよー』ってコメントがあったのでキャッチアップできた
# .find_map{..}
ary.lazy.filter_map{..}.first
  • これは便利なんだけど #filter_map も入ったし find_map ほしいなあ…
  • ちなみに最近以下のようなコードを見かけてしまいつらくなりました
# find で見つかったときにキーを返したい!というコードだが実際には [key, value] を返してしまう…
result = hash.find { |key, value| key if hoge(value) }
  • これ結構実用的なケースだと思うんですけどどうだろうなー
  • もうちょっと具体化してコメントしてみてもいいかもしらん

[Bug #17354] Module#const_source_location is misleading for constants awaiting autoload

  • Ruby 2.7 で Module.const_source_location というメソッドが追加されました
  • これは引数の定数の定義位置を返すメソッドです
# hoge.rb
class Foo
end

# Foo が定義された位置を返す
pp Module.const_source_location(:Foo)
# => ["/home/test/hoge.rb", 2]
  • このメソッドと autoload と組み合わせると .const_source_location 戻り値が意図しない値になるのでは?というバグチケット
# foo.rb
class Foo
end
# hoge.rb
# まだ Foo は定義されていないので nil や false を返す
pp Module.const_defined?(:Foo)         # => nil
pp Module.const_source_location(:Foo)  # => false

# autoload を定義する
autoload :Foo, './foo'

# autoload を定義しているときに const_defined? を呼ぶと true を返すようになる
pp Module.const_defined?(:Foo)
# => true

# この時に .const_source_location は autoload で定義した位置を返す
# 今回はこの戻り値に対してのチケットになる
pp Module.const_source_location(:Foo)
# => ["/home/test/hoge.rb", 7]

# 実際に Foo を参照した場合は .foo.rb が読み込まれて Foo が定義される
pp Module.const_get(:Foo)

pp Module.const_defined?(:Foo)
# => true
pp Module.const_source_location(:Foo)
# => ["/home/test/foo.rb", 2]
  • 上記のケースの Module.const_sjurce_location(:Foo) の戻り値をどうするべきか、みたいな議論がされている
    • [] を返すようにするとか
    • ["./foo", nil] を返すようにするとか `
    • Module#autoload? を使うことで何かしら対処できないかとか
  • autoload 周り無限にきびしそう…

[Feature #17353] Functional chaining operator

  • func(obj) をパイプライン演算子を使って obj |> func とかけるようにする提案
puts(format("the number is %d", make_stuff(gets.to_i)))
# を
gets.to_i
|> make_stuff
|> format "the number is %d"
|> puts
# と書く
  • ぶっちゃけこっちの方のパイプライン演算子はそこまでほしいと思わないんですがどうなんですかねえ
    • Ruby だと func(obj) というよりも objl.func と書くことの方が多いので
  • 以下のように #then を使って書くようなコード例がコメントに書いてあった
gets.to_i
  .then(&method(:make_stuff))
  .then { format "the number is %d", _1 }
  .then(&method(:puts))
  • 乱雑で読みやすいコードとはいえないけどこっちのほうが Ruby っぽくて好きではある

[Bug #17348] Shadowed method can not be evaluated on the line that it is shadowed

  • 以下のようなコードを実行するとエラーになるよー、というバグ報告
def a
  ""
end
# error: undefined method `[]' for nil:NilClass (NoMethodError)
a = a[0]
puts a.inspect
  • これは a = a[0] の行で先に a = nil という変数が定義され、その変数 a に対して [0] でアクセスしようとしているからですね
  • このあたりは Ruby に精通してないと意図しない挙動に見えるので難しい…

[Feature #17342] Hash#fetch_set

  • 次のように Hash#fetch を呼び出しつつ、自身に値を代入したい事がある
homu = { name: "homu" }

# fetch で見つからなかった場合に任意の値を割り当てる
homu.fetch(:age) { homu[:age] = 14 }
pp homu
# => {:name=>"homu", :age=>14}
  • これを行う Hash#fetch_set を追加しようという提案
homu = { name: "homu" }

# fetch で見つからなかった場合にそのキーの要素に割り当てる
homu.fetch_set(:age) { 14 }
pp homu
# => {:name=>"homu", :age=>14}
  • 名前をどうするのかーみたいな議論をされている
    • 雑に #fetch! とかでいいような気もする
  • 実用的なケースで言うと以下みたいな感じ?
# foo.hoge.first が2回呼ばれてしまいつらい
hash.fetch(foo.hoge.first) { hash[foo.hoge.first] = default }
# こっちだと foo.hoge.first が1回しか出てこない
hash.fetch_set(foo.hoge.first) { default }