Я хочу добавить 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
тоже не работает
Использовать функцию prepend
не обязательно, любые предложения, которые помогут добиться желаемого поведения, приветствуются.
🤔 А знаете ли вы, что...
Ruby поддерживает интернационализацию и работу с различными языками и символами.
Kernel
методы, такие как rand
, или Math
, такие как cos
, определяются как так называемые функции модуля (см. модуль_функция), что делает их доступными как в обоих случаях, так и в других случаях.
... (публичные) одноэлементные методы:
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
(который превратил бы все его методы в одноэлементные). Вместо этого каждый метод «функции модуля» определяется дважды: в модуле и в одноэлементном классе модуля:
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
. Это не повлияет на метод cos
, который был определен отдельно в одноэлементном классе Math
. Чтобы также исправить этот метод, вам также придется добавить свой модуль к классу Singleton:
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
в качестве примера, потому что он менее интегрирован, чем Kernel
, но те же правила применяются к «глобальным функциям» из Kernel
.
Что особенного в Kernel
, так это то, что он также включен в main
, который является контекстом выполнения Ruby по умолчанию, т. е. вы можете вызывать rand
без явного получателя.