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

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

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 выдает предупреждение в режиме verbose:

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

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

В данном методе:

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

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

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.

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

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 или лямбда-выражение в аргумент блока с помощью оператора & (преобразования блока):

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

my_method(&argument)

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

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

Method Поиск методов

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

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

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

  • Для совпадающего метода в R

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

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

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

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

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

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