Добавление функции модуля ядра глобально

Я хочу добавить 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 поддерживает интернационализацию и работу с различными языками и символами.


1
96
1

Ответ:

Решено

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 без явного получателя.