古い Ruby の define_method

AtCoder の過去問をやっていて、手元では通るコードがことごとく RE になる理由が全然わからなかった。いろいろ考えてみたが、コードはどう考えても正しい気がする。


ふと、自分は横着して何も考えずに Ruby 2.7.0 を使っていたが、AtCoderRuby は 2.3.3 なことにハッと気づいた。たまたま rbenv で Ruby 2.3.3 を既にインストールしてあったので、$ rbenv local で切り替えて Ruby 2.3.3 で実行してみたところ、何とエラーが出る。これだったのか。

詳しくは書かないけれど、2.7.0 のコードは Module#define_method をこんな風に使っていた。

class Hoge
end

a = 1

Hoge.define_method(:output) do
  puts a
end

Hoge.new.output    #=>1

ローカル変数 a をふつうはスコープの外であるメソッド内と共有しようという意図である。まあ、この手のコード自体がよいものかは別だが、とにかくこれは 2.7.0 で動く。


しかしこれ、2.3.3 ではエラーが出る。何故かというと、2.3.3 では Module#define_method がプライベート・メソッドだからである。つまり、レシーバーを付けて呼び出せないようになっている。
解決方法はある。Object#send を使えばよい。これを使えば、御存知のとおりプライベート・メソッドまで呼び出せてしまう。

Hoge.send(:define_method, :output) do
  puts a
end

これで AtCoder でも動いた。

なお、Module#defile_method がパブリック・メソッドになったのは、たぶん 2.5.0 からである。るりまで調べてみた。