はじめに
Ruby は組込みクラスさえ自由に改変できるけれど,ライブラリーなんかでそんなことをしたら大変なことになってしまう。
そこで,Ruby 2.1 では,既に存在するクラスの改変を限定されたスコープ内でのみ行う仕組みとして,refinement というものが導入された。
導入当初は制約が大きかったが,のちのバージョンで制約が緩められた。
refinement とその制約については既にいくつも記事が書かれてきたが,本記事では(おそらく)まだ書かれていない制約について記述する。
準備
本記事は,文字列をグダグダにする String#sloppy
なるメソッドを導入する(英語苦手なので,メソッド名が適切かは知らん)。
以下に示すように,文字列の文字の並びをシャッフルして返すだけ。
refinement は,まず何らかのクラスを拡張するモジュールを
module MyStringExtension
refine String do
def sloppy
chars.shuffle.join
end
end
end
のように作っておいて,これを適用したい場所で
using MyStringExtension
のようにすれば,そのスコープで拡張が効くようになる。
しかし,モジュールを定義してすぐその場で使うだけなら
using Module.new{
refine String do
def sloppy
chars.shuffle.join
end
end
}
なんて書けばよい。
無名のモジュールを Module.new
で作ってすぐに using
するわけだ。
ここで注意してほしいのが,{ }
を begin end
do end
にしちゃうと,using
のブロックと解釈されちゃうこと。もし do end
を使うなら,using
の引数全体を ( )
で囲まなくてはならない。
これで準備は整った。
ここ以降の説明では,上記の using
がなされたスコープでのコードを示す。
うまくいくケース
当然ながら,
"Ruby".sloppy # => "byRu"
のように使える。
また,
p %w(Ruby Python).map(&:sloppy) # => ["uRby", "ohytPn"]
も OK。
この書き方は,Ruby 2.3 では使えなかった(NoMethodError)が,Ruby 2.4 での制約緩和で使えるようになった。ありがたい。
ブロックを与えるべきメソッドに &:sloppy
を与えると,まず :sloppy#to_proc
で
Proc.new{ |receiver, *args| receiver.send(:sloppy, *args) }
的な Proc
オブジェクトが作られ,それがブロックとして与えられるわけだが,Ruby 2.4 で send
に関する refinement の制約が取り除かれたことでイケるようになったのだろう。
send
の件は下記の拙記事参照:
Refinement で追加したメソッドが send で呼べない - Qiita
うまくいかないケース
さきほどの例では,refinement で追加したメソッドが Symbol#to_proc
で使えることが確認できた。
当然
puts :sloppy.to_proc.call("Ruby")
もイケるだろうと踏んだが,残念ながら NoMethodError
となる。
理由は判らなかった。
追記(2022-03-09) できるようになった
「うまくいかないケース」で挙げたコードを改めて試したところ,
p :sloppy.to_proc.call("Ruby") # => "bRuy"
のように,使えるようになっていた。
少なくとも Ruby 2.7.5 では使える。それ以前のバージョンは調べていない。
この記事を書いたときのバージョンが詳しくは分からない(覚えていない)が,Ruby 2.4 に言及していることから,Ruby 2.4.x と思われる。