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

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

[Bug #7844] include/prepend satisfiable module dependencies are not satisfied

  • 以下のような複雑な継承を行った際に意図する継承リストになっていないというバグ報告
  • ポイントとしては A の前に R が出てきたりするのがおかしくなっていた
    • 解説するのは難しいから実際に脳内で考えてみてね!
module P
end

module Q
  include P
end

module R
  prepend Q
end

module S
  include R
end

class A
  include S
  prepend P
end

p A.ancestors
# Ruby 2.7 => [P, R, A, S, Q, Object, Kernel, BasicObject]
# Ruby 3.0 => [P, A, S, Q, R, Object, Kernel, BasicObject]

[Bug #10845] Subclassing String

  • 以前も紹介したが String を継承したクラスで特定のメソッドを呼ぶと String を返したり継承したクラスを返したりバラバラになっている
class MyString < String
end

# これは MyString のインスタンスを返す
p MyString.new("hoge").capitalize.class
# => MyString
p MyString.new("hoge").strip.class
# => MyString

# 以下のメソッドは String のインスタンスを返す
p (MyString.new("foo") + "bar").class
# => String
p (MyString.new("%d") % 42).class
# => String
class MyString < String
end

# 最新版だと以下の結果になる
p MyString.new("hoge").capitalize.class  # => String
p MyString.new("hoge").strip.class       # => String
p (MyString.new("foo") + "bar").class    # => String
p (MyString.new("%d") % 42).class        # => String
  • 具体的にどのメソッドが対処されたのかは以下の PR を参照
  • またこの影響で ActiveSupport::SafeBuffer が壊れたらしい
  • 一応、非互換な変更なので注意したい

[Feature #17336] using refined: do ... end

  • Foo クラス内でのみ使用できるように拡張する場合、using に直接 Module.new を渡すことがある
class Foo
  # Foo 内でのみ Array#flat_map! が使える
  using Module.new {
    refine Array do
      def flat_map!(&block)
        replace(flat_map(&block))
      end
    end
  }
end
  • 上記の書き方ではネストが深くなってしまう
  • これを解決する案として以下のように Kernel#refined メソッドを経由してネストしないでかけるようにするアイディアもあった
module Kernel
  # 実装イメージ
  def refined(mod, &block)
    Module.new do
      refine(mod, &block)
    end
  end
end

class Foo
  # これは refined のブロック引数になる
  using refined(Array) {
    def flat_map!(&block)
      replace(flat_map(&block))
    end
  }

  # これは using のブロック引数になる
  using refined(Array) do
    def flat_map!(&block)
      replace(flat_map(&block))
    end
  end
end
  • しかし、上記の場合だと do ~ end でかくと using メソッドのブロック引数になってしまう問題がある
class Foo
  # これは using のブロック引数になる
  using refined(Array) do
    def flat_map!(&block)
      replace(flat_map(&block))
    end
  end
end
  • 以下のようにして do ~ end で受け取れるようにするのはどうだろうか、という提案
class Foo
  using refined: Array do
    def flat_map!(&block)
      replace(flat_map(&block))
    end
  end
end
  • もっと簡単に解決できないかな?と思っていたけど do ~ end{} でブロックを渡すメソッドが変わるのが結構つらい
  • 他にも似たようなチケットがあり他の人も書きたい気持ちはあるんだろうけどなかなかいい書き方を見つけるのはむずかしそう…
  • このチケットは Feature #16241 に引き継がれて close されてるっぽい
  • このあたりはいい感じに書きたい気持ちはあるので一回自分でも考えてみたい

[Feature #17339] Semantic grouping with BigDecimal#to_s

  • BigDecimal#to_s では以下のように引数で返ってくる文字列を制御する事ができる
require "bigdecimal"

# 引数がない場合は指数形式
p BigDecimal('1234567').to_s
# => "0.1234567e7"

# 引数が "F" だと指数なしになる
p BigDecimal('1234567').to_s('F')
# => "1234567.0"
  • また、 "3F" のようにすることで任意の文字数で空白区切りすることができる
require "bigdecimal"

# 数値を付けると文字数ごとに空白で区切られる
# これは `先頭` を基準として区切られる
p BigDecimal('1234567').to_s('3F')
# => "123 456 7.0"

# 小数が含まれる場合
p BigDecimal('1234567.8901234').to_s('3F')
# => "123 456 7.890 123 4"
  • このチケットでは "3F" を渡したときに区切られる場所を以下のように変更しようという提案になる
# `先頭` からではなくて小数点からの文字数で区切るようにする提案
p BigDecimal('1234567').to_s('3F')
# => "1 234 567.0"

# 小数が含まれる場合
p BigDecimal('1234567.8901234').to_s('3F')
# => "1 234 567.890 123 4"
  • また長期的に上記のような挙動をデフォルトにしようという提案も含まれているa
  • 国際単位系に準じると提案したような区切りにするのが正しいぽい?
    • チケットにも書いてあるけどよくわからねえ…
  • "F" ではなくて "G" という新しいオプションで制御する話もでてる

[Bug #17216] Enumerator::Chain doesn't support all Enumerator methods

  • Enumerable#chain を使用すると複数の Enumerator を 1つの Enumerator として扱うことができる
p [1, 2, 3].chain((4..5), "a".."c").to_a
# => [1, 2, 3, 4, 5, "a", "b", "c"]
  • Enumerable#chainEnumerator::Chainインスタンスを返すのだが Enumerator::Chain#with_index を呼び出すとエラーになってしまうというバグ報告
# error: wrong argument type chain (expected enumerator) (TypeError)
[1, 2, 3].chain([4, 5, 6]).with_index.to_a

# each を経由すると OK
[1, 2, 3].chain([4, 5, 6]).each.with_index.to_a

merge, fix されたチケット