2021/02/25 今週の気になった bugs.ruby のチケット

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

[PR #4193] Add Hash#delete_at

  • Hash#delete_at を追加する提案
    • bugs.ruby にまだチケットはなさそう
  • 特定のキーの要素を取り除いて、取り除いた要素を返すメソッド
hash = { a: true, b: false, c: nil }
# hash から直接要素を取り除きつつ、取り除いた値を返す
a, c = hash.delete_at(:a, :c) # => [ true, nil ]
hash # => { b: false }

# Hash#except は取り除かれた結果を返す
hash = { a: true, b: false, c: nil }
hash = hash.except(:a, :c)
p hash
# {:b=>false}
  • 取り除いた要素を参照したい場合は便利そう

[PR #207] Support pattern matching in CSV rows

  • CSV::Row をパターンマッチに対応させる PR
    • CSV の行ごとにパターンマッチできる
  • 以下のようにヘッダーカラムがキーで要素が値になる
header = [:id, :name, :age]
case CSV::Row.new(header, [1, "homu", 14])
in name:, age:
  name # => "homu"
  age  # => 14
end
  • これは普通に便利そう
  • こういうパターンマッチ対応って今後流行っていくんですかねー

[Bug #17590] M.prepend M has hidden side effect

  • 先週話していたバグ
  • 以下のように M.prepend M を呼んだ場合に副作用あるバグが修正された
module M; end
class C; end

C.prepend M
C.include M

# これを読んだ時に失敗するが、副作用があった
M.prepend M rescue nil

module M2; end
M2.prepend M
C.include M2

p C.ancestors
# 3.0 => [M, C, M2, M, M2, Object, Kernel, BasicObject]
# 3.1 => [M, C, M2, Object, Kernel, BasicObject]

[Bug #17649] defined? invokes method once for each syntactic element around it

  • defined? の式で複数回メソッドが呼ばれることがあるというバグ報告
  • 以下の例だと x メソッドが defined? 時に複数回呼ばれることがある
public def x
  $times_called += 1
end

def times_called
  $times_called = 0
  yield
  $times_called
end

# without `defined?`
times_called { x }           # => 1
times_called { -x }          # => 1
times_called { --x }         # => 1
times_called { ---x }        # => 1
times_called { x+0+0 }       # => 1
times_called { x.pred.pred } # => 1
times_called { x.x.x.x.x.x } # => 6

# with `defined?`
times_called { defined? x }           # => 0
times_called { defined? -x }          # => 1
times_called { defined? --x }         # => 2
times_called { defined? ---x }        # => 3
times_called { defined? x+0+0 }       # => 2
times_called { defined? x.pred.pred } # => 2
times_called { defined? x.x.x.x.x.x } # => 15
  • これは defined? a.b.c.d を呼び出した時に a a.b a.b.c a.b.c.d が個別に呼び出されしまっているからのようです
puts RubyVM::InstructionSequence.new("defined? a.b.c.d").disasm
__END__
output:
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: TRUE)
== catch table
| catch type: rescue st: 0001 ed: 0039 sp: 0000 cont: 0041
| == disasm: #<ISeq:defined guard in <compiled>@<compiled>:0 (0,0)-(-1,-1)> (catch: FALSE)
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] $!@0
| 0000 putnil
| 0001 leave
|------------------------------------------------------------------------
0000 putnil                                                           (   1)[Li]
0001 putself
0002 defined                                func, :a, false
0006 branchunless                           41
0008 putself
0009 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0011 defined                                method, :b, false
0015 branchunless                           41
0017 putself
0018 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0020 opt_send_without_block                 <calldata!mid:b, argc:0, ARGS_SIMPLE>
0022 defined                                method, :c, false
0026 branchunless                           41
0028 putself
0029 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0031 opt_send_without_block                 <calldata!mid:b, argc:0, ARGS_SIMPLE>
0033 opt_send_without_block                 <calldata!mid:c, argc:0, ARGS_SIMPLE>
0035 defined                                method, :d, true
0039 swap
0040 pop
0041 leave