Kernel#iterator?で警告を出す

結論

ブロックを渡されているかどうかをチェックして真偽値を返すKernel#iterator?をdeprecatedにしようぜという話

Kernel#iterator?

Kernel#iterator?はブロックが渡されているかどうかをチェックできるメソッドです。

def check
  if iterator?
    puts "Block is given."
  else
    puts "Block isn't given."
  end
end
check{} #=> Block is given.
check #=> Block isn't given.

サンプルコードはるりまからお借りしました。

docs.ruby-lang.org

ただし、渡されたブロックが必ずイテレートするわけではないため、Kernel#iterator?を使うのは推奨されないみたい。
ちなみに、とくに警告などはでないという……。

なので、Kernel#iterator?を実行するとdeprecatedであると警告するようにCRubyにパッチを当ててみた。

まず、vm_eval.c内にあるrb_f_block_given_p()を見てみます。
rb_f_block_given_p()がブロックが渡されているかをチェックしているCの関数です。

/*
 *  call-seq:
 *     block_given?   -> true or false
 *     iterator?      -> true or false
 *
 *  Returns <code>true</code> if <code>yield</code> would execute a
 *  block in the current context. The <code>iterator?</code> form
 *  is mildly deprecated.
 *
 *     def try
 *       if block_given?
 *         yield
 *       else
 *         "no block"
 *       end
 *     end
 *     try                  #=> "no block"
 *     try { "hello" }      #=> "hello"
 *     try do "hello" end   #=> "hello"
 */


static VALUE
rb_f_block_given_p(VALUE _)
{
    rb_execution_context_t *ec = GET_EC();
    rb_control_frame_t *cfp = ec->cfp;
    cfp = vm_get_ruby_level_caller_cfp(ec, RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp));

    if (cfp != NULL && VM_CF_BLOCK_HANDLER(cfp) != VM_BLOCK_HANDLER_NONE) {
	return Qtrue;
    }
    else {
	return Qfalse;
    }
}

ちなみに実装は上記のような感じになっています。

今回はこのrb_f_block_given_p()を使って警告を出すCの関数を以下のように作成します。

static VALUE
rb_f_iterator_p(VALUE _)
{
    rb_warn_deprecated("iterator?", "block_given?");
    return rb_f_block_given_p(_);
}

rb_warn_deprecated()は第一引数にdeprecatedになったメソッド名を渡し、第二引数に代わりに使用する方が良いメソッド名を渡します。
なので、ここではiterator?の代わりにblock_given?を使うように推奨しています。

あとはvm_eval.cの末尾にあるInit_vm_eval関数内にあるKernel#iterator?の定義を以下のように変更します。

rb_define_global_function("iterator?", rb_f_iterator_p, 0);

rb_define_global_functionは第一引数にメソッド名を受け取り、第二引数で実行するCの関数、第三引数に定義されるメソッドが受け取る引数の数を指定しています。
またrb_define_global_functionで定義されたメソッドはKernelモジュール内にmodule_functionとして定義されます。

あとはmake && make installを実行してビルドします。

make && make install

ビルドしたRubyで以下のコードを実行すると非推奨の警告が出るようになります。

def check
  if iterator?
    puts "Block is given."
  else
    puts "Block isn't given."
  end
end
check{} #=> Block is given.
check #=> Block isn't given.
bug.rb:2: warning: iterator? is deprecated; use block_given? instead
Block is given.
bug.rb:2: warning: iterator? is deprecated; use block_given? instead
Block isn't given.

こんな感じで非推奨の警告が出るようになりました。

おわりに

Kernel#iterator?は、そんなに使われていないメソッドなので非推奨の警告を出していいんじゃないかなぁとは思う。
まだチケットを作って提案するかは考えているところだけど、非推奨で良いんじゃないかなぁ……。

もしくはRuby3.0のリリースに合わせて廃止しちゃってもいいかもしれないね。