Spec-Zone.ru › Ruby 3.4

Уточнения

Из-за открытых классов Ruby вы можете переопределять или добавлять функциональность к существующим классам. Это называется «патчем обезьяны». К сожалению, область действия таких изменений глобальна. Все пользователи класса, подвергшегося патчу обезьяны, видят те же изменения. Это может вызвать непреднамеренные побочные эффекты или сбои в программах.

Уточнения разработаны для уменьшения воздействия патчей обезьяны на других пользователей класса, подвергшегося патчу. Уточнения предоставляют способ локального расширения класса. Уточнения могут изменять как классы, так и модули.

Вот базовая уточнение:

class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

Сначала определен класс C. Затем создается уточнение для C с помощью Module#refine.

Module#refine создаёт анонимный модуль, содержащий изменения или уточнения для класса (C в примере). self в блоке refine — это анонимный модуль, аналогичный Module#module_eval.

Активировать уточнение можно следующим образом:

using M

c = C.new

c.foo # prints "C#foo in M"

Область действия

Вы можете активировать уточнения на верхнем уровне, а также внутри классов и модулей. Вы не можете активировать уточнения в области действия метода. Уточнения активируются до конца текущего определения класса или модуля или до конца текущего файла, если они используются на верхнем уровне.

Вы можете активировать уточнения в строке, переданной в Kernel#eval. Уточнения активны до конца строки eval.

Уточнения имеют лексическую область действия. Уточнения активны только в области действия после вызова using. Любой код до инструкции using не будет иметь активированного уточнения.

При передаче управления за пределы области действия уточнение деактивируется. Это означает, что если вы требуете или загружаете файл или вызываете метод, определённый за пределами текущей области действия, уточнение будет деактивировано:

class C
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

def call_foo(x)
  x.foo
end

using M

x = C.new
x.foo       # prints "C#foo in M"
call_foo(x) #=> raises NoMethodError

Если метод определен в области действия, где активен уточнение, уточнение будет активным при вызове метода. Этот пример охватывает несколько файлов:

c.rb:

class C
end

m.rb:

require "c"

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

m_user.rb:

require "m"

using M

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) # prints "C#foo in M"
x.foo              #=> raises NoMethodError

Поскольку уточнение M активно в m_user.rb, где определён MUser#call_foo, оно также активно, когда main.rb вызывает call_foo.

Поскольку использование — это метод, уточнения активны только при его вызове. Вот примеры, где уточнение M активно и неактивно.

В файле:

# not activated here
using M
# activated here
class Foo
  # activated here
  def foo
    # activated here
  end
  # activated here
end
# activated here

В классе:

# not activated here
class Foo
  # not activated here
  def foo
    # not activated here
  end
  using M
  # activated here
  def bar
    # activated here
  end
  # activated here
end
# not activated here

Обратите внимание, что уточнения в M не активируются автоматически, если класс Foo будет повторно открыт позже.

В eval:

# not activated here
eval <<EOF
  # not activated here
  using M
  # activated here
EOF
# not activated here

При невычислении:

# not activated here
if false
  using M
end
# not activated here

При определении нескольких уточнений в одном модуле внутри нескольких refine блоков, все уточнения из одного модуля активны при вызове метода с уточнением (любой из методов to_json из примера ниже):

module ToJSON
  refine Integer do
    def to_json
      to_s
    end
  end

  refine Array do
    def to_json
      "[" + map { |i| i.to_json }.join(",") + "]"
    end
  end

  refine Hash do
    def to_json
      "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}"
    end
  end
end

using ToJSON

p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]"

Method Поиск

При поиске метода для экземпляра класса C Ruby проверяет:

  • Если для C активны уточнения, в обратном порядке их активации:

    • Модули, добавленные впереди от уточнения для C

    • Уточнение для C

    • Модули, добавленные сзади от уточнения для C

  • Модули, добавленные впереди от C

  • C

  • Модули, добавленные сзади от C

Если метод не был найден на каком-либо этапе, это повторяется с суперклассом C.

Обратите внимание, что методы в подклассе имеют приоритет над уточнениями в суперклассе. Например, если метод / определен в уточнении для Numeric 1 / 2 вызывает исходный Integer#/, потому что Integer является подклассом Numeric и ищется до уточнений для суперкласса Numeric. Поскольку метод / также присутствует в дочернем Integer, поиск метода не поднимается до суперкласса.

Однако, если метод foo определен в уточнении для Numeric, 1.foo вызывает этот метод, поскольку foo не существует в Integer.

super

При вызове super поиск метода проверяет:

  • Модули, добавленные сзади от текущего класса. Обратите внимание, что текущий класс может быть уточнением.

  • Если текущий класс — уточнение, поиск метода продолжается, как в разделе «Поиск метода» выше.

  • Если у текущего класса есть непосредственный суперкласс, метод выполняется, как в разделе «Поиск метода» выше, используя суперкласс.

Обратите внимание, что super в методе уточнения вызывает метод в уточняемом классе, даже если существует другое уточнение, которое было активировано в том же контексте. Это верно только для super в методе уточнения, это не относится к super в методе в модуле, который включён в уточнение.

Интроспекция методов

При использовании методов интроспекции, таких как Kernel#method или Kernel#methods, уточнения не учитываются.

Это поведение может быть изменено в будущем.

Refinement наследование с помощью Module#include

Когда модуль X включается в модуль Y, Y наследует уточнения от X.

Например, C наследует уточнения от A и B в следующем коде:

module A
  refine X do ... end
  refine Y do ... end
end
module B
  refine Z do ... end
end
module C
  include A
  include B
end

using C
# Refinements in A and B are activated here.

Уточнения в потомках имеют больший приоритет, чем уточнения предков.

Дополнительные материалы

См. github.com/ruby/ruby/wiki/Refinements-Spec для текущего спецификации реализации уточнений. Спецификация также содержит больше подробностей.

Ruby Core © 1993–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API