2022/04/21 今回の気になった bugs.ruby のチケット

今週はレシーバに対して Kernel#p が呼び出せるようにする提案がありました。

[Feature #18736] self-p for method chain

  • 次のようにメソッドチェーンの間にレシーバを出力したいという要望チケット
class Object
  def sp(method=nil, *args, &block)
    if method
        Kernel.p self.public_send(method, *args, &block)
    elsif block_given?
        Kernel.p block.call(self)
    else
      Kernel.p self
    end
    return self
  end
end

# sp のレシーバを標準出力する
p [1,2,3].map{|x| x**2}.sp.map{|x| x**2}
# output:
# [1, 4, 9]
# [1, 16, 81]

# ブロックの結果を出力する
[1,2,3].sp{|x| "my List = #{x}"}
# output:
# "my List = [1, 2, 3]"

# レシーバに対して sum(-10) を呼び出した結果を出力する
[1,2,3].sp(:sum,-10)
# output:
# -4
  • #tap すると以下のように書くことができる
# sp のレシーバを標準出力する
p [1,2,3].map{|x| x**2}.tap(&method(:p)).map{|x| x**2}
# output:
# [1, 4, 9]
# [1, 16, 81]

# ブロックの結果を出力する
[1,2,3].tap{|x| p "my List = #{x}"}
# output:
# "my List = [1, 2, 3]"

# レシーバに対して sum(-10) を呼び出した結果を出力する
[1,2,3].tap { p _1.sum(-10) }
# output:
# -4

[Bug #18729] Method#owner and UnboundMethod#owner are incorrect after using Module#public/protected/private

  • 次のように public / protected / private を呼び出した後に #owner が変わってないというバグ報告
class A
  protected def foo
    :A
  end
end

class B < A
  p [instance_method(:foo), instance_method(:foo).owner, instance_methods(false), A.instance_methods(false)]
  public :foo
  p [instance_method(:foo), instance_method(:foo).owner, instance_methods(false), A.instance_methods(false)]
end
# outptu:
# [#<UnboundMethod: B(A)#foo() owner.rb:2>, A, [], [:foo]]
# [#<UnboundMethod: B(A)#foo() owner.rb:2>, A, [:foo], [:foo]]
  • Bfoo が再定義されているのに ownerB(A)#foo() のままになっている
  • 期待する挙動は以下の通り
[#<UnboundMethod: B(A)#foo() owner.rb:2>, A, [], [:foo]]
[#<UnboundMethod: B#foo() owner.rb:2>, B, [:foo], [:foo]]

[Bug #18741] Slicing an Array using index out of range inconsistent return type

  • 範囲外の値を Array#[] に渡したときの挙動が一貫していないというバグ報告
 def test_slicing_arrays
   array = [:peanut, :butter, :and, :jelly]

   assert_equal [:peanut], array[0,1]
   assert_equal [:peanut, :butter], array[0,2]
   assert_equal [:and, :jelly], array[2,2]
   assert_equal [:and, :jelly], array[2,20]
   assert_equal [], array[4,0] # 範囲外を指定した時にこっちは [] を返す
   assert_equal [], array[4,100]
   assert_equal nil, array[5,0] # 同様に範囲外を指定しているがこっちは nil を返す
 end

[Feature #18742] Introduce a way to tell if a method invokes the super keyword

  • メソッド内で super を呼び出しているかどうかを判定するメソッドを追加する提案
class X
  def a
  end; p instance_method(:a).calls_super? #=> false

  def b
    super
  end; p instance_method(:b).calls_super? #=> true

  def c
    super if false
  end; p instance_method(:c).calls_super? #=> true

  def d
    eval 'super'
  end; p instance_method(:d).calls_super? #=> false (I doubt there's a reasonable way for this to return true)
end

2022/04/14 今回の気になった bugs.ruby のチケット

今週は Kernel#then に引数を渡せるようにする提案がありました。

[Feature #18690] Allow Kernel#then to take arguments

  • Kernel#then に引数を追加する提案
  • 通常はレシーバをブロックの引数として受け取るが
1.5.then{|x| Math.atan(x)}
  • #then に渡した引数をブロックの引数でも受け取るようにする提案
3.then(4){|x, y| Math.hypot(x, y)}
  • チケットでは
honyarara.then{|x|
  foo(x)
  bar(fugafugafuga)
  baz(hogehogehoge)
  qux(x, fugafugafuga, hogehogehoge)
}
  • の場合に
honyarara.then(fugafugafuga, hogehogehoge){|x, y, z|
  foo(x)
  bar(y)
  baz(x)
  qux(x, y, z)
}
  • とかけるユースケースが提示されている
  • then の中で複数回同じメソッドを呼ぶ場合だと便利そうですかね?
obj.bar.then(hoge.foo) { |bar, foo| foo.piyo(foo, bar, foo) }

[Bug #18688] when array's default value is empty hash adding a hash key value changes all array elements

  • 以下のように空の Hash の配列の要素を書き換えると全て書き変わってしまうというバグ報告
# {} が3つある配列を生成する
ah = Array.new(3, {})
p ah    # => [{}, {}, {}]

# 1つの要素を書き換える
ah[1][:foo] = 'bar'

# この時に配列の要素は以下のようになる
p a
# 期待する挙動 => [{}, {:foo=>"bar"}, {}]
# 実際の挙動   => [{:foo=>"bar"}, {:foo=>"bar"}, {:foo=>"bar"}]
  • これは配列の要素の参照先が全て同じになっているので意図する挙動になる
# これは参照しているオブジェクトがバラバラ
a = [{}, {}, {}]
p a[0].__id__   # => 60
p a[1].__id__   # => 80
p a[2].__id__   # => 100

# Array.new の場合は全て同じオブジェクトを参照している
a = Array.new(3, {})
p a[0].__id__   # => 120
p a[1].__id__   # => 120
p a[2].__id__   # => 120


# n 個の同じ要素の配列を定義したい場合はブロックを渡して定義するのが安全
a = Array.new(3) { {} }
p a[0].__id__   # => 60
p a[1].__id__   # => 80
p a[2].__id__   # => 100

[Feature #18685] Enumerator.product: Cartesian product of enumerables

  • 要素ごとの全ての組み合わせの Enumerator を生成する Enumerator.product を追加する提案
product = Enumerator.product(1..3, ["A", "B"])
p product.class #=> Enumerator

product.each do |i, c|
  puts "#{i}-#{c}"
end

=begin output
1-A
1-B
2-A
2-B
3-A
3-B
=end
  • Array#product はあるんですがそれとは別に Enumerable でも使いたいって感じですかね
# Enumerable に対しては使えないので Array に変換する必要がある
pp (1..3).to_a.product(["A", "B"])
# => [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"], [3, "B"]]

[Feature #18618] no clobber def

  • 以下のようにスーパークラスのメソッドがある場合は再定義しないようなコードがある
class Dog
  def bark
    'bark!'
  end
end

class Poodle < Dog
  # 既に同名のメソッドが定義されていれば例外にする
  raise if method_defined? :bark
  def bark
    'bow-wow'
  end
end
  • これを次のように ncdef でかけるようにしたいという提案
class Dog
  def bark
    'bark!'
  end
end

class Poodle < Dog
  ncdef bark        # "no clobber" def
    'bow-wow'
  end
end

# => #<MethodAlreadyDefined: Method `bark' already defined.>
  • ApplicationRecord とかのサブクラスを定義したい時にこういうことをしたいらしい
  • 意図しないメソッドの上書きってどれぐらい弊害があるんですかね…
    • これがあると全部 ncdef で定義する必要がありそう
  • 他の言語だと逆に『スーパークラス側でメソッドが上書きできないこと』を明示化するような機能は存在する
  • C++ の例だと以下のような感じ
// 基底クラス
class base {
  // final が付いていると派生クラスで再定義できない
  virtual void func_final() final;
};

// 派生クラス
class sub : base {
  // error: error: declaration of 'func_final' overrides a 'final' function
  virtual void func_final() final;
};

2022/04/09 今回の気になった bugs.ruby のチケット

今週は MatchData#[] のバグ報告などがありました。

[Bug #18670] MatchData#[start, length] pads nil values when negative start is given

  • MatchData#[] にマイナス値を渡すと nil で埋めされた配列を返すバグ報告
# [-1, 5] は最後の値から5文字分を返す

# String#[] や Array#[] は結果的に最後の値だけを返す
pp "hello"[-1, 5]     # => "o"
pp [1, 2, 3][-1, 5]   # => "3"

# MatchData#[] の場合は足りない値が nil 埋めされた配列を返す
result = /.*/.match("aaaa")
pp result          # => #<MatchData "aaaa">
pp result[-1, 5]   # => ["aaaa", nil, nil, nil, nil]
  • このバグは修正済み
# MatchData#[] の場合は足りない値が nil 埋めされた配列を返す
result = /.*/.match("aaaa")
pp result[-1, 5]
# Ruby 3.1 => ["aaaa", nil, nil, nil, nil]
# Ruby 3.2 => ["aaaa"]

[Feature #18683] Allow to create hashes with a specific capacity.

  • Hash オブジェクトを生成する時にオブジェクトのサイズを指定できるようにする提案
    • 更に rb_hash_new_capa(long) も拡張する必要がある
hash = Hash.new(capacity: 1000)
  • StringArray には同様の機能があるので Hash にも欲しいという意図らしい
String.new(capacity: XXX)
Array.new(XX) / rb_ary_new_capa(long)

[Bug #18677] BigDecimal#power (**) returns FloatDomainError when passing an infinite parameter

  • BigDecimal#power (**) に無限大を渡すと意図しないエラーになるというバグ報告
require "bigdecimal"

# error: `**': Computation results in 'Infinity' (FloatDomainError)
BigDecimal(10) ** BigDecimal("Infinity")
  • サンプル実装も書かれているんですが意図としては BigDecimal::INFINITY を返してほしい感じですかね?
require "bigdecimal/util"

class BigDecimal < Numeric
  def **(other)
    if other.infinite? == 1
      if self > 1
        BigDecimal::INFINITY
      elsif self == 1
        self
      elsif self >= 0
        BigDecimal(0)
      else
        power(other)
      end
    else
      power(other)
    end
  end
end

def puts_and_eval(string)
  puts string
  p eval(string)
end

puts_and_eval "10 ** BigDecimal::INFINITY"    # => Infinity
puts_and_eval "1 ** BigDecimal::INFINITY"     # => 0.1e1
puts_and_eval "0.1 ** BigDecimal::INFINITY"   # => 0.0
puts_and_eval "0 ** BigDecimal::INFINITY"     # => 0.0
puts_and_eval "-1 ** BigDecimal::INFINITY"    # => -0.1e1

[Bug #18673] Anonymous block forwarding fails when combined with keyword arguments

  • 以下のようにキーワード引数が定義されている時に匿名のブロック引数を別のメソッドに渡すとエラーになるバグ報告
def inner
  "yielded #{yield}"
end

def block_only(&)
  # OK
  inner(&)
end

def pos_arg(arg1, &)
  # OK
  inner(&)
end

def kwarg(arg1, kwarg1:, &)
  # NG
  inner(&)
end

def kwarg_with_default(arg1, kwarg1: "kwarg_default", &)
  # NG
  inner(&)
end
  • このバグは修正済み

[Bug #18415] String#rpartition is not sufficiently greedy compared to String#partition

  • 以下のような貪欲な正規表現String#rpartition に渡すと意図する値が返ってこないバグ報告
# [最初のセパレータより前の部分, セパレータ, それ以降の部分] を返す
pp "...123...".partition /\d+/
# => ["...", "123", "..."]

# [最後のセパレータより前の部分, セパレータ, それ以降の部分] を返すが意図した値ではない
pp "...123...".rpartition /\d+/
# => ["...12", "3", "..."]
pp "...123...".rpartition(/(?<!\d)\d+/)
# => ["...", "123", "..."]

Ruby 3.2.0 Preview1 が出た

Ruby 3.2.0 Preview1 が出ました。
RUby 3.1.0 Preview1 が去年の11月に出たことを考えると Ruby 3.2 の Preview1 は出るのが早かったですね?

Ruby 3.2.0 Preview1 の新機能としては WASIベースのWebAssemblyサポートRegexp timeout になりますね。
気になる人は試してみるとよいと思います。

余談1: git protocol が廃止された

rbenv で Ruby 3.2.0 Preview1 をインストールするために ruby-build を更新しようとするとエラーが出た。

$ git pull
fatal: リモートエラー:
  The unauthenticated git protocol on port 9418 is no longer supported.
Please see https://github.blog/2021-09-01-improving-git-protocol-security-github/ for more information.

これは暗号化されてない git protocol が廃止されたためらしい。
雑に remote 先を変えて対応。

$ git remote -v
origin  git://github.com/sstephenson/ruby-build.git (fetch)
origin  git://github.com/sstephenson/ruby-build.git (push)
$ git remote set-url origin https://github.com/rbenv/ruby-build.git
$ git remote -v
origin  https://github.com/rbenv/ruby-build.git (fetch)
origin  https://github.com/rbenv/ruby-build.git (push)

余談2:Ruby 3.2.0 から libyaml が同梱されなくなった

数日前から rbenv install 3.2.0-dev すると require': cannot load such file -- digest (LoadError) になってビルドに失敗していました。
これ、結構困っていたんですが原因は Ruby 3.2.0 から libyaml が同梱されなくなったからでした。
なので sudo apt install libyaml-dev する事で解決しました。
これは Ruby 3.2.0 Preview1 のリリースノートに書いてあったから気づけたんですが、読まなかった一生わからなかったなあ。
同様に Ruby 3.2.0 Preview2 から libffi が同梱されなくなるらしいので注意する必要があります。

Ruby で新しい Unicode 規格にバージョンアップする時になにを行っているのかまとめてみた

Ruby は定期的に Unicode の新しい規格に対応しているんですがその時になにが行われているのかが気になったので調べてみました。
この記事では『Ruby の Unicode 14.0.0 対応がなにを行っているか』を追っていきたいと思います。
ちなみに Unicode 14.0.0 は次の Ruby 3.2 でサポートされる予定です。

RubyUnicode 14.0.0 対応

RubyUnicode 14.0.0 対応のチケットは以下になります。

上のチケットの作業コミットはコメントに書かれているんですが以下の通りです。

commit 48f1e8c5d85043e6adb8e93c94532daa201d42e9
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Tue Mar 15 18:55:16 2022 +0900

    Fix version check to use Emoji version for emoji-variation-sequences.txt

commit 56d9d78f14b73cb9f609558e6b760dde50872fb6
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Tue Mar 15 17:17:15 2022 +0900

    Remove Unicode 13.0.0 related files

commit 267f0089d3255c1f06ab5adf9f6c77b1ccfd2771
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Mon Mar 14 08:37:53 2022 +0900

    clarify meaning of version guards for Unicode version specs [ci skip]

commit 1b571d0abf6070673320b11a30769bbe74d12e39
Author: Benoit Daloze <eregontp@gmail.com>
Date:   Sun Mar 13 13:18:56 2022 +0100

    Fix guards for unicode versions specs

commit 45187a0fcddecc74dacc1881f2405a5ebe198081
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Sun Mar 13 10:52:24 2022 +0900

    comment out failing Unicode/Emoji version checks temporarily

commit 8f59482f5d1f77542fe3c8e8434cb1dbbc764ecf
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Sat Mar 12 21:33:51 2022 +0900

    add some tests for Unicode Version 14.0.0

commit 9b545b0caf2ccc89718ba02ff631d2a68b96a831
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 17:18:42 2022 +0900

    update specs to check for Unicode Version 14.0.0/Emoji Version 14.0

commit 2672502457523317268ac24704cf85df91e2cae6
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 17:11:32 2022 +0900

    mention Unicode Version 14.0.0

commit 8e1f3a96aecb3defc34556d75e3d2a0867416082
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 16:48:26 2022 +0900

    switch UNICODE_BETA back to NO

commit 45e0711f29f9ce65cd34ad14e3af1546ccc7252e
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Thu Dec 9 16:41:09 2021 +0900

    update Unicode Version to 14.0.0 and Emoji version to 14.0

と、いうことでまずはこのコミットの内容を下から時系列に1つ1つ見ていこうかな、と思います。

[45e0711f29] update Unicode Version to 14.0.0 and Emoji version to 14.0

common.mkUNICODE_VERSIONUNICODE_EMOJI_VERSION のバージョンが 14 に更新されています。
UNICODE_EMOJI_VERSION が別にあるんですね?
また作業当時はまだ beta 版だったので UNICODE_BETA = YES となっているぽい?
他には enc/unicode/14.0.0/casefold.henc/unicode/14.0.0/name2ctype.h の2つのファイルが新しく追加されて lib/unicode_normalize/tables.rb が更新されています。
このあたりのファイルはあとで詳しくみていきましょうかね。

[8e1f3a96ae] switch UNICODE_BETA back to NO

common.mkUNICODE_BETANO になっています。

[2672502457] mention Unicode Version 14.0.0

NEWS.md にドキュメントを追加しています。

[9b545b0caf] update specs to check for Unicode Version 14.0.0/Emoji Version 14.0

RbConfig::CONFIG['UNICODE_VERSION']RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] に対するテストが追加されています。
これでバージョンを取得できるんですね、へー。

p RbConfig::CONFIG['UNICODE_VERSION']
# Ruby 3.1 => # "13.0.0"
# Ruby 3.2 => # "14.0.0"

p RbConfig::CONFIG['UNICODE_EMOJI_VERSION']
# Ruby 3.1 => # "13.1"
# Ruby 3.2 => # "14.0"

UNICODE_VERSIONUNICODE_EMOJI_VERSION で同じバージョンとは限らないんですね、へー。

[9b545b0caf] add some tests for Unicode Version 14.0.0

正規表現\p (Unicode プロパティによる文字クラス指定) のと String#upcase のテストが追加されています。

[45187a0fcd] comment out failing Unicode/Emoji version checks temporarily

テストをコメントアウトしています。何かあったんですかね?

[1b571d0abf] Fix guards for unicode versions specs

テストのコメントアウトを元に戻しています。

[267f0089d3] clarify meaning of version guards for Unicode version specs [ci skip]

テストのインデントを調整したりコメントを追加したりしています。

[56d9d78f14] Remove Unicode 13.0.0 related files

enc/unicode/13.0.0/casefold.henc/unicode/13.0.0/name2ctype.h を削除しています。
この2つはバージョンごとに新しいファイルを追加している感じですかね?

[48f1e8c5d8] Fix version check to use Emoji version for emoji-variation-sequences.txt

テストで参照しているバージョンを UNICODE_VERSION から EMOJI_VERSION に変更しています。
ただ、これは別のコミットで元に戻されているぽいですね。


と、いう感じでコミットを1つ1つみてみました。
実際の Unicode 対応としては、

  • enc/unicode/14.0.0/casefold.h
  • enc/unicode/14.0.0/name2ctype.h
  • lib/unicode_normalize/tables.rb

の3つのファイルが重要そうですね。
今度はこの3つのファイルがなにをやっているのか見ていきたいと思います。

enc/unicode/14.0.0/casefold.h

enc/unicode/14.0.0/casefold.h には Unicode の大文字、小文字の対応表が定義されています。
例えば Unicode 14.0.0 で追加された U+2C2F という文字も enc/unicode/14.0.0/casefold.h に新しく追加されています。

# Ruby 3.2 だとちゃんと小文字に変換される
p "\u2C2F".downcase.codepoints.map { _1.to_s(16) }
# Ruby 3.1 => ["2c2f"]
# Ruby 3.2 => ["2c5f"]

この enc/unicode/14.0.0/casefold.h ファイルは CaseFolding.txt を元に指定 enc/unicode/case-folding.rb機械的に生成されているみたいです。

enc/unicode/14.0.0/name2ctype.h

enc/unicode/14.0.0/name2ctype.h には正規表現POSIX 文字クラスの [:alpha:][:lower:]Unicode プロパティなどのメタデータが定義されています。
例えば Unicode 14.0.0 で追加された U+0870 という文字も enc/unicode/14.0.0/name2ctype.h に新しく追加されています。

p "\u0870" =~ /[[:alpha:]]/
# Ruby 3.1 => nil
# Ruby 3.2 => 0

この enc/unicode/14.0.0/name2ctype.h ファイルは tool/enc-unicode.rb から機械的に生成されているぽい?

lib/unicode_normalize/tables.rb

lib/unicode_normalize/tables.rbenc/unicode/14.0.0/casefold.henc/unicode/14.0.0/name2ctype.h とは違って新しくファイルが追加されているわけではなくて既存のファイルを更新しています。
lib/unicode_normalize/tables.rb には Unicode 正規化を行うためのメタデータが定義されています。
例えば NFKC で正規化した際の ① => 1 という変換テーブルもここで定義されています。

p "".unicode_normalize(:nfkc)
# => "1"

Unicode 14.0.0 で追加された U+A7F2 という文字も lib/unicode_normalize/tables.rb に追加されており、この文字を正規化すると "C" になります。

p "\uA7F2".unicode_normalize(:nfkc)
# Ryvt 3.1 => "\uA7F2"
# Ruby 3.2 => "C"

lib/unicode_normalize/tables.rb ファイルもまた template/unicode_norm_gen.tmpl を元にして機械的に生成されているぽいですかね?

まとめ

と、いう感じで簡単にですが RubyUnicode 14.0.0 に対応した時になにをしているのかを簡単にまとめてみました。
Ruby 側で対応している項目自体は多い(Unicode のアップデート内容による)んですが全体的に自動生成されるような仕組みがあるようなのですごい。
あと大文字小文字の変換テーブルや正規表現POSIX 文字クラス、Unicode 正規化のメタ情報も Ruby の処理系で保持しているんですね。
具体的にこのあたりのデータがどう使用されているのかあんまりわかっていないので次はこのあたりの実装がどうなっているのか調べてみるのとこあおもしろそうですねー。
次は Unicode 15.0.0 の対応もはじまると思うのでその時にまたどういうような対応を行っているのか追えるとよいですねー。

2022/03/31 今回の気になった bugs.ruby のチケット

今週は正規表現マッチするときのタイムアウトを導入するチケットがマージされました。

[Feature #17837] Add support for Regexp timeouts

  • 正規表現を比較する時にタイムアウトの概念を導入するチケット
  • 色々と議論があったが最終的にはタイムアウトの時間を制御する Regexp.timeout Regexp.timeout= が追加された
  • Regexp.timeoutタイムアウトする時刻(秒)を設定する事で『正規表現を比較する時に設定したタイムアウト秒を越えると例外が発生する』ようになる
    • デフォルトだと nil になっており nil だと例外は発生しない
# デフォルトだと nil
pp Regexp.timeout
# => nil

# この場合はタイムアウトしない
/A(B|C+)+D/ =~ "A" + "C" * 28 + "X"

# タイムアウトする時刻を設定する事ができる
Regexp.timeout = 0.1
pp Regexp.timeout
# => 0.1

# 正規表現の比較に 0.1秒以上かかると例外が発生する
begin
  /A(B|C+)+D/ =~ "A" + "C" * 28 + "X"
rescue => e
  pp e
  # => #<Regexp::TimeoutError: regexp match timeout>
end
  • また Regexp.newRegexp.compile もキーワード引数 timeout: を受け取るようになたt
# デフォルトだと timeout: nil
regexp = Regexp.new("A(B|C+)+D")
regexp =~ "A" + "C" * 28 + "X"

# 正規表現ごとに個別に timeout を設定することができる
regexp2 = Regexp.new("A(B|C+)+D", timeout: 0.5)
begin
  regexp2 =~ "A" + "C" * 28 + "X"
rescue => e
  pp e
  # => #<Regexp::TimeoutError: regexp match timeout>
end

[Feature #12655] Accessing the method visibility

  • メソッドの可視性の情報として『 [:public, :protected, :private, :undefined, :overridden] を取得したい』という要望のチケット
    • looksee という gem でそういう機能を実装している
    • 今はがんばって C拡張で実装しているが RubyAPI でもほしいらしい
  • :undefined の情報を取得する手段として Module#undefined_instance_methods が新しく追加される予定
class Super
  def hoge
  end
end

class Sub
  undef hoge
end

pp Sub.undefined_instance_methods
# => [:hoge]

[Feature #15357] Proc#parameters returns incomplete type information

  • 以下のようにブロックの引数で『オプショナル引数でないのに Proc#parameters:opt になる』のは正しくないんじゃないか、というバグ報告
# これは b がオプショナル引数
pr = proc { |a, b = 2| [a,b] }

# しかし Proc#parameters は a もオプショナル引数として情報を返す
p pr.parameters  # => [[:opt, :a], [:opt, :b]]

# 同様に以下のような場合も b もオプショナル引数として情報が返ってくる
pr = proc { |a = 1, b| [a,b] }
p pr.parameters  # => [[:opt, :a], [:opt, :b]]
  • これは proc の引数は全てオプショナル引数として定義されているためである
# オプショナル引数として定義していない a に引数を渡さなくてもエラーにはならない
pr = proc { |a, b = 2| [a,b] }
p pr.call
# => [nil, 2]

# こちらも同様
pr = proc { |a = 1, b| [a,b] }
p pr.call
# => [1, nil]
  • なので報告されたコード自体は意図する挙動になっている
  • ちなみに lambda の場合は必須引数として返ってくる
pr = lambda{|a, b=2| [a,b] }
p pr.parameters  # => [[:req, :a], [:opt, :b]]

pr = lambda{|a=1, b| [a,b] }
p pr.parameters  # => [[:opt, :a], [:req, :b]]
  • また、このように lambda であるようにパラメータ情報を受け取る手段として Proc#parameterslambda: false キーワード引数が追加された
    • これは既に Ruby 3.2 で対応済み
pr = proc{|a, b=2| [a,b] }
p pr.parameters                # => [[:opt, :a], [:opt, :b]]
p pr.parameters(lambda: true)  # => [[:req, :a], [:opt, :b]]

pr = proc{|a=1, b| [a,b] }
p pr.parameters                # => [[:opt, :a], [:opt, :b]]
p pr.parameters(lambda: true)  # => [[:opt, :a], [:req, :b]]

2022/03/24 今回の気になった bugs.ruby のチケット

今週は注釈付き代入演算子を提案するチケットなどがありました。

[Feature #18626] 注釈付き代入演算子 ()= の提案

  • 以下のような注釈付き代入演算子の提案になる
class Object
  # 代入するときの処理をフックする
  def self.()= (what)
    what.is_a? self or raise TypeRestrictionError
  end
end

# 代入する時に注釈を指定して定義する
age (Fixnum) = 30

# メソッドの引数にも定義できる
def add(a(Numeric), b(Numeric))
  a + b
end
add 1, "2" # raises TypeRestrictionError
  • この提案なんですが以下のような理由から Reject されています

文法的にも意味論的にも大変興味深い提案ですが、Ruby言語にいかなる形であれ型宣言や型注釈を導入するつもりはありません。すみません。

まつもと ゆきひろ /:|)

  • これは面白いなー既存の構文を壊さないようにしてなんか定義できないだろうか

[Feature #18644] Coerce anything callable to a Proc

  • 呼び出し可能オブジェクトを拡張するためのメソッドを追加する提案
  • #call メソッドを Proc 化する Object#to_proc
class Object
  # call メソッドを `Proc` オブジェクト化する
  def to_proc
    return method(:call).to_proc if respond_to?(:call)

    raise "Needs to respond to :call"
  end
end

class Proc
  def to_proc
    self
  end
end

callable.to_proc.curry[value]
  • 引数を Proc オブジェクト化する Kernel#Proc
class Kernel
  def Proc(value)
    if value.is_a?(::Proc)
      value
    elsif value.respond_to?(:call)
      value.method(:call).to_proc
    else
      raise "Needs to implement :call"
    end
  end
end

Proc(callable).curry[value]
  • #call メソッドがあるオブジェクトを Proc 化したいケースってどれぐらいあるんですかね

[Feature #18640] default empty string argument for String#sub and String#sub!, e.g. "hello".sub("l")

  • String#sub(pattern, replace)replace のデフォルト値を "" にする提案
# "hello".sub("l", "") と同じ意味になる
p "hello".sub("l")
# => "helo"
  • sub って名前なのに引数が1つしかないのがちょっと気持ち悪いなあ