カーネルモジュール関数をグローバルに先頭に追加する
概要
次のように Kernel.rand を先頭に追加したいと思います。
# I try something like
mod = Module.new do
def rand(*args)
p "do something"
super(*args)
end
end
Kernel.prepend(mod)
# And I expect this behaviour
Kernel.rand #> prints "do something" and returns random number
rand #> prints "do something" and returns random number
Object.new.send(:rand) #> prints "do something" and returns random number
残念ながら、上記のコードは期待どおりに動作しません。 Kernel.singleton_class を先頭に追加しても機能しません
先頭に追加機能を使用する必要はありません。目的の動作を実現するのに役立つ提案は歓迎です。
解決策
rand などのカーネル メソッドや cos などの Math メソッドは、いわゆるモジュール関数 (module_function を参照) として定義されており、両方として使用できます。
… (パブリック) シングルトン メソッド:
Math.cos(0) # <- `cos' called as singleton method
#=> 1.0
…および (プライベート) インスタンス メソッド:
class Foo
include Math
def calc
cos(0) # <- `cos' called from included module
end
end
foo = Foo.new
foo.calc
#=> 1.0
foo.cos(0) # <- not allowed
# NoMethodError: private method `cos' called for #<Foo:0x000000010e3ab510>
これを実現するために、Math のシングルトン クラスには、単に Math が含まれるわけではありません (これにより、すべてのメソッドがシングルトン メソッドに変換されます)。代わりに、各「モジュール関数」メソッドは、モジュール内とモジュールのシングルトン クラス内で 2 回定義されます。
Math.private_instance_methods(false)
#=> [:ldexp, :hypot, :erf, :erfc, :gamma, :lgamma, :sqrt, :atan2, :cos, ...]
# ^^^
Math.singleton_class.public_instance_methods(false)
#=> [:ldexp, :hypot, :erf, :erfc, :gamma, :lgamma, :sqrt, :atan2, :cos, ...]
# ^^^
結果として、別のモジュールを Math の前に追加したり、Math にパッチを当てたりすると、一般に (プライベート) インスタンス メソッドにのみ影響するため、Math を含むクラスにのみ影響します。 Math のシングルトン クラスで別途定義された cos メソッドには影響しません。そのメソッドにもパッチを適用するには、モジュールをシングルトン クラスの先頭にも追加する必要があります。
module MathPatch
def cos(x)
p 'cos called'
super
end
end
Math.prepend(MathPatch) # <- patch classes including Math
Math.singleton_class.prepend(MathPatch) # <- patch Math.cos itself
これにより、次のようになります。
Math.cos(0)
# "cos called"
#=> 1.0
同様に:
foo.calc
# "cos called"
#=> 1.0
ただし、副作用として、インスタンス メソッドもパブリックになります。
foo.cos(0)
# "cos called"
#=> 1.0
Math を例として取り上げたのは、Math がカーネルほど統合されていないためですが、カーネルの「グローバル関数」にも同じルールが適用されます。
カーネルの特別な点は、カーネルが Ruby のデフォルトの実行コンテキストである main にも含まれていることです。つまり、明示的なレシーバーなしで rand を呼び出すことができます。