RubyのRefinement(翻訳: 公式ドキュメントより)

こんにちは、hachi8833です。 自分の理解のためも兼ねて、Ruby 2.1から正式に導入されたRefinementのドキュメントを翻訳しました。適宜強調などを行っていますのでご了承ください。訳語はほぼまったく定着していないので、英語のrefinementで表記します。 refinementの理解は#usingメソッドの動作とスコープの理解にかかっていると感じました。#usingを書いた位置から下でrefinementが効くというあたりは、(機能は違いますが)privateキーワードと少し似ているように思います。 当時は知りませんでしたが、refinementの導入はかなり大変だったようです。以下も合わせてどうぞ。 るびま: Refinementsとは何だったのか Refinement 英語ドキュメント: https://ruby-doc.org/core-2.4.0/doc/syntax/refinements_rdoc.html Rubyはオープンクラスなので、既存のクラスを再定義したり機能を追加したりできます。この手法は「モンキーパッチ(monkey patch)」と呼ばれています。残念なことに、モンキーパッチによる変更のスコープはグローバルなので、モンキーパッチのあたったクラスのユーザーすべてに影響が生じます。こうしたモンキーパッチが原因で、プログラムで意図しない副作用が発生することがあります。 refinementはモンキーパッチがクラスの利用者に与える影響を軽減するために設計され、クラスの拡張をローカルにとどめるための手段を提供します。 以下はrefinementの基本的な利用法です。 class C def foo puts “C#fooです” end end module M refine C do def foo puts “MのC#fooです” end end end 最初にCというクラスを定義し、次にModule#refineでCをrefinementするという順序になります。refinementで変更できるのはクラスのみであり、モジュールをrefinementすることはできません。つまり、Module#refineの引数に与えられるのはクラスだけです。 Module#refineによって無名のモジュールが作成されます。このモジュールにはクラス(ここではC)に対する変更やrefinementが含まれます。refineブロック内の#selfがその無名モジュールであり、Module#module_evalと似ています。 refinementを有効にするには#usingを使います。 using M c = C.new c.foo # “MのC#fooです”が出力される スコープ (#usingで)refinementを有効にできるスコープは次の3つです。 トップレベル クラス内 モジュール内 メソッドのスコープ内ではrefinementを有効にできません。 refinementが有効なのは、現在のクラス定義またはモジュール定義が終わるまでの間です。 トップレベルの場合は、現在のファイルの最下部までとなります。 Kernel#evalに渡す文字列内でrefinementを有効にすることもできます。この場合、refinementはevalされる文字列内でのみ有効となります。 refinementはスコープ内でレキシカル(≒静的)に動作します。refinementは#using呼び出し後、そのスコープ内でのみ有効となります。#usingより上の部分のコードではrefinementは無効です。 制御がそのスコープの外に移ると、refinementは無効になります。つまり、refinementを含むファイルをrequireしたり読み込んだりしても、refinementの現在のスコープの外で定義されているメソッドを呼び出したりしても、スコープ外でrefinementが有効になることはありません。 class C end module M refine C do def foo puts “MのC#fooです” end end end def call_foo(x) x.foo end using M # ここより上のコードにはrefinementが効かない x = C.new x.foo # “MのC#fooです”が出力される call_foo(x) #=> raises NoMethodError refinementが有効なスコープ内でメソッドが定義されている場合、そのメソッドへの呼び出しは有効になります。次の4つのファイルに分かれたコード例をご覧ください。 c.rb: class C end m.rb: require “c” module M refine C do def foo puts “MのC#fooです” end end end m_user.rb: require “m” using M # ここから下はrefinementが効く class MUser def call_foo(x) x.foo end end main.rb: require “m_user” x = C.new m_user = MUser.new m_user.call_foo(x) # “MのC#fooです”が出力される x.foo #=> raises NoMethodError 上のコードでは、MUser#call_fooが定義されているm_user.rbファイル内でMのrefinementが有効になっているので、main.rbでの#call_foo呼び出しも有効になります。 #usingはメソッドであり、このメソッドが呼び出されてからはじめてrefinementが有効になります。Mのrefinementがどこで有効になり,どこで無効になるかを次に示します。 ファイルaの中 # (無効) using M # 有効 class Foo # 有効 def foo # 有効 end # 有効 end # 有効 クラスの中 # (無効) class Foo # (無効) def foo # (無効) end using M # 有効 def bar … Continue reading RubyのRefinement(翻訳: 公式ドキュメントより)