Содержание
Создание функций является основополагающим в нашей профессии разработчика. В Ruby нам повезло иметь язык, который очень гибко подходит к составлению определения этих методов.
В этой статье мы рассмотрим все различные типы аргументов, которые может принимать функция.
Позиционные аргументы
Позиционные аргументы являются наиболее часто используемыми и определяются в порядке их появления. Они напрямую связаны с порядком параметров метода. Давайте рассмотрим пример:
def add(a, b, c) puts a + b + c end add(1, 2, 3) # => 6.
Вы также можете присвоить им значение по умолчанию с помощью этого синтаксиса:
def add(a, b = 2, c = 3) puts a + b + c end add(1) # => 6.
⚠️ Примечание: Невозможно обернуть позиционный аргумент вокруг двух позиционных аргументов со значением по умолчанию.
def add(a = 1, b, c = 3) # => SyntaxError puts a + b + c end.
Аргументы ключевых слов
Аргументы ключевых слов позволяют явно указать, для какой переменной предназначено то или иное значение. Это делает вызов метода более читабельным и гибким. Вот как это работает:
def greet(name, age)
puts "Hello #{name}! Вам #{age} лет."
end
greet("Alice", 30) # => Здравствуйте Alice! Вам 30 лет.
Вы можете смешивать позиционные и ключевые аргументы:
def greet(name, age)
puts "Hello #{name}! Вам #{age} лет."
end
greet("Alice", 30) # => Hello Alice! Тебе 30 лет.
⚠️ Примечание: Невозможно поместить позиционный аргумент после аргумента ключевого слова.
def greet(name, age)
puts "Hello #{name}! Вам #{age} лет."
end
Большим преимуществом использования Keywords является то, что он не выделяет память под отправленный ему Hash. Я объясню это на примере:
def greet(options = {})
puts "Hello #{options.fetch(:name)}! Вам #{options.fetch(:age)} лет."
end
def greet(name:, age:)
puts "Hello #{name}! Вам #{age} лет."
end
greet({name: "Alicia", age: 33})
greet(name: "Alicia", age: 33)
Позиционные и ключевые аргументы - два наиболее часто используемых типа аргументов. До сих пор мы не видели истинной силы Ruby. Прежде чем двигаться дальше, давайте вспомним некоторые правила использования аргументов Positional и Keywords.
Когда использовать Positional / Keyword?
Для этого у меня есть простое правило:
Если мой метод публичный → Аргумент по ключевому слову Примеры методов, вызываемых вне контекста моего класса, метод initialize, метод call для объектов Service Objects. Если мой метод приватный → Позиционный аргумент Методы внутри класса служат для разгрузки действий, выполняемых в публичных методах. Поскольку мы полностью контролируем вызывающих и вызываемых, мы можем позволить себе использовать только позиционные аргументы.
Именованные остаточные аргументы
Оператор Splat (*) и оператор Double Splat (**) помогают нам управлять переменным количеством аргументов, которые мы получаем в нашей функции.
Давайте рассмотрим пример:
def display_info(*args, **kwargs)
puts "Positional Arguments: #{args}"
puts "Аргументы ключевых слов: #{kwargs}"
end
display_info(1, 2, {fake: :love}, name: "Bob", age: 25)
# => Positional Arguments: [1, 2, {:fake=>:love}]
# => Аргументы ключевых слов: {:name=>"Bob", :age=>25}
*args означает: Взять все позиционные аргументы и поместить их в переменную args в виде списка.
**kwargs означает: Взять все аргументы ключевых слов и поместить их в переменную kwargs в виде хэша.
Переменные args и kwargs названы так по соглашению; вы можете переименовать их по своему усмотрению.
Аргументы анонимного отдыха
В предыдущем примере мы видели, что происходит, когда мы называем использование Splat-операторов в определении метода.
На самом деле есть и второе применение операторов splat, когда мы не присваиваем их ничему:
def process_data(*args, processing_method: "default")
puts "Обработка методом: #{processing_method}"
end
process_data("Hello", "World", processing_method: "custom") # => Обработка методом: custom
В примере выше вы видели, что с отправленными позиционными аргументами ничего не было сделано.
Аналогичное поведение наблюдается и с оператором Double Splat:
def process_data(card, method, **options)
puts "#{card} была списана методом #{method}"
end
process_data("Hello", "World", processing_method: "custom") # => Hello была списана методом World
Но тогда вы, наверное, задаетесь вопросом, а зачем это нужно?
Делегируйте полномочия базовым функциям, игнорируя содержимое аргументов. Игнорируйте ”лишние” аргументы и не вызывайте исключение.
Давайте рассмотрим пример, который объясняет эти два варианта использования:
def greet(**args)
greet_kindly(**args)
greet_wickedly(**args)
end
def greet_kindly(name:, **)
puts "Hi #{name}, nice to see you!"
end
def greet_wickedly(name:, surname:, **)
puts "Hi #{name}, what a stupid surname #{surname}…"
end
greet(name: "Hugo", surname: "The V")
# Output:
# Hi Hugo, nice to see you!
# Hi Hugo, what a stupid surname The V…
Метод greet_kindly получил имя и фамилию, но извлек имя и отправил фамилию в пустоту thуказывает на нотацию **. Без ** будет выдана ошибка ArgumentError.
Существует еще один метод делегирования аргументов; он называется Argument Forwarding, с нотацией ...:
def greet
# method body
end
def greet_kindly
# method body
end
def greet_wickedly
# method body
end
Разница лишь в том, что * и ** рассматривают только один тип аргумента (позиционный / ключевой), а ... рассматривает все типы аргументов.
Аргументы оператора Nil Splat
Как вы думаете, если объединить оператор Double Splat и nil, что произойдет?
def greet(name, **nil)
puts "Hello #{name}!"
end
greet("Charlie", other: "argument?")
Я дам вам ответ: возникает исключение нет принятых ключевых слов (ArgumentError).
Чем это полезно?
Вы будете удивлены, узнав, что аргументы-ключи обладают очень интересным свойством. Если метод ожидает позиционный аргумент, а присылает аргумент с ключевым словом, то аргумент с ключевым словом преобразуется в хэш и переводится в ранг позиционного аргумента.
def greet(name)
puts "Hello #{name}!"
end
greet("Hugo") # => Hello Hugo!
Если вы хотите предотвратить такое поведение в ваших методах, вам нужно добавить **nil в конце объявления параметров!
Аргумент блока
Этому разделу можно было бы посвятить целую статью. Управление блоками - это то, что делает Ruby таким ценным.
Суть блочного аргумента заключается в возможности делегировать часть внутреннего тела вашего метода вовне. Это обеспечивает невероятную гибкость. И что самое приятное? Вы можете напрямую отправлять информацию в блок:
def do_action
yield(1, 2)
end
do_action do |a, b|
puts "What does #{a} + #{b} mean? #{a + b}"
end
В этом примере я использовал свой блок анонимно, но вы также можете назвать его следующим образом:
def do_action(&block)
yield block
end
do_action do
"Hello World!"
end
# => "Hello World!"
Это очень простые примеры, но помните, что блок-аргументы открывают огромный спектр возможностей в ваших приложениях.
TL;DR
Вот краткое описание каждого типа аргументов, рассмотренных сегодня!
- Позиционные аргументы
(a, b, c) - Аргументы-ключи
(имя:, возраст:) - Именованные остаточные аргументы
(*args, **kwargs) - Анонимные остаточные аргументы
(*, **) - Переадресация аргументов
(...) - nil kwargs
(**nil) - Аргумент блока
(&block)
Вот и все на сегодня!
Не стесняйтесь экспериментировать с различными типами аргументов в своих проектах на Ruby и делиться своими открытиями. Освоив эти тонкости, вы сможете разрабатывать более надежные и гибкие функции, действительно отвечающие потребностям ваших приложений.