Spec-Zone.ru › Ruby 3.4

Вызов методов

Вызов метода отправляет объекту сообщение, чтобы он выполнил некоторую работу.

В Ruby сообщение объекту отправляется так:

my_method()

Обратите внимание, что круглые скобки необязательны:

my_method

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

В этом разделе рассматривается только вызов методов. См. также документацию по синтаксису определения методов.

Получатель

self является получателем по умолчанию. Если вы не укажете получателя, будет использоваться self. Чтобы указать получателя, используйте .:

my_object.my_method

Это отправляет сообщение my_method объекту my_object. Получателем может быть любой объект, но в зависимости от видимости метода отправка сообщения может вызвать NoMethodError.

Для обозначения получателя также можно использовать ::, но этот способ используется редко из-за возможной путаницы с ::, применяемым для пространств имён.

Цепочка вызовов методов

Можно «связывать» вызовы методов в цепочку, сразу вызывая следующий метод после предыдущего.

В этом примере в цепочку объединены методы Array#append и Array#compact:

a = [:foo, 'bar', 2]
a1 = [:baz, nil, :bam, nil]
a2 = a.append(*a1).compact
a2 # => [:foo, "bar", 2, :baz, :bam]

Подробности:

  • Первый метод merge создаёт копию a, по отдельности добавляет каждый элемент a1 в эту копию и возвращает

    [:foo, "bar", 2, :baz, nil, :bam, nil]
    
  • Следующий в цепочке метод compact создаёт копию этого возвращённого значения, удаляет элементы со значением nil и возвращает

    [:foo, "bar", 2, :baz, :bam]
    

В цепочку можно объединять методы из разных классов. В этом примере в цепочку объединены методы Hash#to_a и Array#reverse:

h = {foo: 0, bar: 1, baz: 2}
h.to_a.reverse # => [[:baz, 2], [:bar, 1], [:foo, 0]]

Подробности:

  • Первый метод Hash#to_a преобразует a в массив и возвращает

    [[:foo, 0], [:bar, 1], [:baz, 2]]
    
  • Следующий в цепочке метод Array#reverse создаёт копию этого возвращённого значения, разворачивает её и возвращает

    [[:baz, 2], [:bar, 1], [:foo, 0]]
    

Оператор безопасной навигации

&., называемый «оператором безопасной навигации», позволяет пропустить вызов метода, если получатель — nil. Он возвращает nil и не вычисляет аргументы метода, если вызов пропущен.

REGEX = /(ruby) is (\w+)/i
"Ruby is awesome!".match(REGEX).values_at(1, 2)
# => ["Ruby", "awesome"]
"Python is fascinating!".match(REGEX).values_at(1, 2)
# NoMethodError: undefined method `values_at' for nil:NilClass
"Python is fascinating!".match(REGEX)&.values_at(1, 2)
# => nil

Это позволяет легко объединять в цепочку методы, которые могут возвращать пустое значение. Обратите внимание, что &. пропускает только один следующий вызов, поэтому в более длинной цепочке необходимо добавлять оператор на каждом уровне:

"Python is fascinating!".match(REGEX)&.values_at(1, 2).join(' - ')
# NoMethodError: undefined method `join' for nil:NilClass
"Python is fascinating!".match(REGEX)&.values_at(1, 2)&.join(' - ')
# => nil

Аргументы

При отправке сообщения используются три типа аргументов: позиционные аргументы, именованные аргументы (или аргументы с ключевыми словами) и аргумент-блок. Для каждого отправляемого сообщения можно использовать один, два или все типы аргументов, но передавать их нужно именно в этом порядке.

В Ruby все аргументы передаются по ссылке и вычисляются сразу.

Аргументы разделяются ,:

my_method(1, '2', :three)

Аргументом может быть выражение или аргумент-хеш:

'key' => value

либо аргумент с ключевым словом:

key: value

Hash и аргументы с ключевыми словами должны идти подряд и располагаться после всех позиционных аргументов, но их можно смешивать:

my_method('a' => 1, b: 2, 'c' => 3)

Позиционные аргументы

Позиционные аргументы сообщения указываются после имени метода:

my_method(argument1, argument2)

Во многих случаях при отправке сообщения круглые скобки не нужны:

my_method argument1, argument2

Однако круглые скобки необходимы, чтобы избежать неоднозначности. Это вызовет SyntaxError, поскольку Ruby не понимает, какому аргументу метода следует передать argument3:

method_one argument1, method_two argument2, argument3

Если определение метода содержит *argument, дополнительные позиционные аргументы будут назначены argument в методе в виде Array.

Если определение метода не включает аргументы с ключевыми словами, аргументы с ключевыми словами или типа «хеш» назначаются последнему аргументу как единый хеш:

def my_method(options)
  p options
end

my_method('a' => 1, b: 2) # prints: {'a'=>1, :b=>2}

Если передано слишком много позиционных аргументов, возникает ArgumentError.

Позиционные аргументы по умолчанию

Если в методе определены аргументы по умолчанию, передавать методу все аргументы не требуется. Ruby подставит недостающие аргументы по порядку.

Сначала рассмотрим простой случай, когда аргументы по умолчанию указаны справа. Рассмотрим следующий метод:

def my_method(a, b, c = 3, d = 4)
  p [a, b, c, d]
end

Здесь c и d имеют значения по умолчанию, которые Ruby подставит за вас. Если отправить этому методу только два аргумента:

my_method(1, 2)

Ruby выведет [1, 2, 3, 4].

Если отправить три аргумента:

my_method(1, 2, 5)

Ruby выведет [1, 2, 5, 4]

Ruby подставляет недостающие аргументы слева направо.

Ruby позволяет указывать значения по умолчанию и в середине списка позиционных аргументов. Рассмотрим более сложный метод:

def my_method(a, b = 2, c = 3, d)
  p [a, b, c, d]
end

Здесь b и c имеют значения по умолчанию. Если отправить этому методу только два аргумента:

my_method(1, 4)

Ruby выведет [1, 2, 3, 4].

Если отправить три аргумента:

my_method(1, 5, 6)

Ruby выведет [1, 5, 3, 6].

Объяснить это словами сложно и запутанно. Вместо этого я опишу происходящее через переменные и значения.

Сначала 1 присваивается a, затем 6 присваивается d. Остаются только аргументы со значениями по умолчанию. Поскольку 5 ещё не присвоено значение, оно передаётся в b, а для c используется значение по умолчанию 3.

Аргументы с ключевыми словами

Аргументы с ключевыми словами указываются после любых позиционных аргументов и разделяются запятыми, как позиционные аргументы:

my_method(positional1, keyword1: value1, keyword2: value2)

Для всех неуказанных аргументов с ключевыми словами используются значения по умолчанию из определения метода. Если указан аргумент с ключевым словом, которого нет в списке метода, а определение метода не допускает произвольные аргументы с ключевыми словами, возникает ArgumentError.

Значение аргумента с ключевым словом можно не указывать; в этом случае значение будет получено из контекста по имени ключа

keyword1 = 'some value'
my_method(positional1, keyword1:)
# ...is the same as
my_method(positional1, keyword1: keyword1)

Обратите внимание: если круглые скобки метода тоже опущены, порядок разбора может оказаться неожиданным:

my_method positional1, keyword1:

some_other_expression

# ...is actually parsed as
my_method(positional1, keyword1: some_other_expression)

Аргумент-блок

Аргумент-блок передаёт методу замыкание из области видимости вызывающего кода.

При отправке сообщения методу аргумент-блок всегда указывается последним. Блок передаётся методу с помощью do ... end или { ... }:

my_method do
  # ...
end

или:

my_method {
  # ...
}

do end имеет более низкий приоритет, чем { }, поэтому:

method_1 method_2 {
  # ...
}

Блок передаётся в method_2, тогда как:

method_1 method_2 do
  # ...
end

Блок передаётся в method_1. Обратите внимание: в первом случае, если используются круглые скобки, блок передаётся в method_1.

Блок принимает аргументы от метода, которому он был передан. Аргументы определяются так же, как аргументы метода. Аргументы блока указываются в | ... | после открывающего do или {:

my_method do |argument1, argument2|
  # ...
end

Локальные аргументы блока

Также можно объявить локальные для блока аргументы с помощью ; в списке аргументов блока. Присваивание значения локальному аргументу блока не переопределяет локальные аргументы вне блока в области видимости вызывающего кода:

def my_method
  yield self
end

place = "world"

my_method do |obj; place|
  place = "block"
  puts "hello #{obj} this is #{place}"
end

puts "place is: #{place}"

Будет выведено:

hello main this is block
place is world

Таким образом, переменная place в блоке — не та же переменная place, что и вне блока. Если удалить ; place из аргументов блока, получится следующий результат:

hello main this is block
place is block

Распаковка позиционных аргументов

Рассмотрим следующий метод:

def my_method(argument1, argument2, argument3)
end

С помощью оператора * (или splat-оператора) можно преобразовать Array в список аргументов:

arguments = [1, 2, 3]
my_method(*arguments)

или:

arguments = [2, 3]
my_method(1, *arguments)

Оба варианта эквивалентны следующему:

my_method(1, 2, 3)

Оператор распаковки * можно применять к любому объекту, а не только к массивам. Если объект отвечает на вызов метода to_a, этот метод вызывается и должен вернуть Array; элементы этого массива передаются как отдельные позиционные аргументы:

class Name
  def initialize(name)
    @name = name
  end

  def to_a = @name.split(' ')
end

name = Name.new('Jane Doe')
p(*name)
# prints separate values:
#   Jane
#   Doe

Если у объекта нет метода to_a, сам объект передаётся как один аргумент:

class Name
  def initialize(name)
    @name = name
  end
end

name = Name.new('Jane Doe')
p(*name)
# Prints the object itself:
#   #<Name:0x00007f9d07bca650 @name="Jane Doe">

Это позволяет полиморфно обрабатывать один или несколько аргументов. Также обратите внимание, что для nil определён NilClass#to_a, возвращающий пустой массив, поэтому возможна условная распаковка:

my_method(*(some_arguments if some_condition?))

Если метод to_a существует, но не возвращает Array, при распаковке возникнет ошибка:

class Name
  def initialize(name)
    @name = name
  end

  def to_a = @name
end

name = Name.new('Jane Doe')
p(*name)
#  can't convert Name to Array (Name#to_a gives String) (TypeError)

Также можно использовать ** (описанный далее), чтобы преобразовать Hash в аргументы с ключевыми словами.

Если количество объектов в Array не совпадает с количеством аргументов метода, возникает ArgumentError.

Если splat-оператор стоит первым в вызове, необходимо использовать круглые скобки, чтобы избежать неоднозначности между оператором распаковки и оператором умножения. В этом случае Ruby выдаёт предупреждение в подробном режиме:

my_method *arguments  # warning: '*' interpreted as argument prefix
my_method(*arguments) # no warning

Распаковка аргументов с ключевыми словами

Рассмотрим следующий метод:

def my_method(first: 1, second: 2, third: 3)
end

С помощью оператора ** (оператора распаковки ключевых слов) можно преобразовать Hash в аргументы с ключевыми словами:

arguments = { first: 3, second: 4, third: 5 }
my_method(**arguments)

или:

arguments = { first: 3, second: 4 }
my_method(third: 5, **arguments)

Оба варианта эквивалентны следующему:

my_method(first: 3, second: 4, third: 5)

Оператор распаковки ** можно применять к любому объекту, а не только к хешам. Если объект отвечает на вызов метода to_hash, этот метод вызывается и должен вернуть Hash; элементы этого хеша передаются как аргументы с ключевыми словами:

class Name
  def initialize(name)
    @name = name
  end

  def to_hash = {first: @name.split(' ').first, last: @name.split(' ').last}
end

name = Name.new('Jane Doe')
p(**name)
# Prints: {name: "Jane", last: "Doe"}

В отличие от оператора *, ** вызывает ошибку, если применяется к объекту, который не отвечает на вызов to_hash. Единственное исключение — nil: этот объект явно не определяет данный метод, но его всё равно можно использовать при распаковке **, не добавляя никаких аргументов с ключевыми словами.

Это также позволяет выполнять условную распаковку:

my_method(some: params, **(some_extra_params if pass_extra_params?))

Как и оператор *, ** вызывает ошибку, если объект отвечает на вызов to_hash, но не возвращает Hash.

Если в определении метода используется оператор распаковки ключевых слов для сбора произвольных аргументов с ключевыми словами, они не будут собраны с помощью *:

def my_method(*a, **kw)
  p arguments: a, keywords: kw
end

my_method(1, 2, '3' => 4, five: 6)

Вывод:

{:arguments=>[1, 2], :keywords=>{'3'=>4, :five=>6}}

Proc: преобразование в блок

Рассмотрим метод, использующий блок:

def my_method
  yield self
end

С помощью оператора & (оператора преобразования в блок) можно преобразовать proc или lambda в аргумент-блок:

argument = proc { |a| puts "#{a.inspect} was yielded" }

my_method(&argument)

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

my_method &argument  # warning
my_method(&argument) # no warning

Поиск метода

При отправке сообщения Ruby ищет для получателя метод, имя которого совпадает с именем сообщения. Методы хранятся в классах и модулях, поэтому при поиске обходятся именно они, а не сами объекты.

Порядок поиска метода для класса или модуля получателя R:

  • Модули, добавленные перед R, в обратном порядке

  • Поиск подходящего метода в R

  • Подключённые модули R в обратном порядке

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

После нахождения совпадения поиск метода прекращается.

Если совпадение не найдено, поиск повторяется с начала, но для method_missing. По умолчанию method_missing — это BasicObject#method_missing, который при вызове вызывает NameError.

Если активны рефинменты (экспериментальная функция), поиск метода изменяется. Подробнее см. в документации по рефинментам.

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

Spec-Zone.ru

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