Понимание процедур, лямбда-выражений и блоков в Ruby.
Часть 2: Объяснение всех причин.
В первой части этой статьи мы узнали, что такое Procs, Lambdas и Blocks? Если вы не читали ее, я настоятельно рекомендую вам прочитать Часть 1: Все ответы, прежде чем продолжить.
В этой части давайте рассмотрим, зачем нам в первую очередь нужны Procs, Lambdas и Blocks и насколько полезно их использование, на нескольких примерах и вариантах использования.
Давайте начнем…
Почему блоки?
Подведение итогов. Блок – это фрагмент кода, заключенный либо в фигурные скобки
{}
, либо вdo..end
. Он похож на метод, но не принадлежит ни одному объекту и не имеет имени.
Чтобы ответить, почему блоки? и чтобы понять необходимость их использования, мы должны ответить еще на один вопрос — Можно ли использовать идентификатор/ссылку метода ruby в качестве первоклассного значения или прошло без дополнительных действий?
Нет. Методы Ruby не являются функциями первого класса, в отличие от нескольких других языков, скажем, Javascript.
Идентификатор обычной функции в Ruby (которая на самом деле является методом) не может быть использован в качестве значения или передан. Сначала его необходимо извлечь в объект
Method
илиProc
для использования в качестве данных первого класса. — вики
Чтобы использовать его в качестве значения первого класса, сначала его необходимо захватить в объект Method
или Proc
.
# Retrieving method reference into Method object def some_method puts "Called by Reference" end method_reference = method(:some_method) # <Method: main.some_method> # Calling the method using its reference def some_other_method(method_ref_argument) # Do something before method_ref_argument.call # Do some other things end some_other_method(method_reference) # returns "Called by Reference"
С другой стороны, блоки можно создавать «на ходу», и нам не нужно беспокоиться о ссылках. Попробуем добиться того же, что и выше, с блоками.
# Block - No method reference capturing bullsh*t def some_other_method(&block_argument) # Do something before block_argument.call # Do some other things end some_other_method { puts "I am Block! and you are?" } # returns "I am Block! and you are?"
При таком подходе нам не нужно создавать метод каждый раз, когда мы хотим, чтобы какие-то нестандартные действия происходили в разных методах.
Ну, а можно ли вообще назначать/ссылаться на блоки? Да, которые мы называем процедурами и лямбда-выражениями — не беспокойтесь об этом сейчас, мы обсудим в следующих разделах.
P.S. Если вас интересует символ &
перед аргументом метода, вот прекрасно написанная статья, объясняющая это.
У вас есть точка зрения?
Один метод можно заставить работать по-разному, внедрив некоторый пользовательский код внутри метода извне.
Другими словами, использование блока возвращает контроль программисту.
Возьмем простой пример использования….
Система поддержки клиентов для поставщика услуг для управления заявками в службу поддержки, созданными клиентами. Давайте просто сосредоточимся на статусе заявки и проигнорируем другие функции.
Билет имеет один из open
, on_hold
или closed
статусов. У нас есть конечная точка API для отметки соответствующего статуса заявок.
Обычно в нашем контроллере мы реализуем, как показано ниже
class TicketsController < ApplicationController ..... def mark_open if @ticket.update(status: "open") # request response logic else # error handler end end def mark_on_hold if @ticket.update(status: "on_hold") # request response logic else # error handler end end def mark_closed if @ticket.update(status: "closed") # request response logic else # error handler end end ...... end
Мы можем заметить повторяющийся код для каждого метода для обработки ответа и ошибок. Основное действие этих методов — обновление статуса, не говоря уже об обработке запросов и ответов.
Используя блоки, мы можем абстрагировать запрос-ответ и обработку ошибок в новый метод, сохраняя действия чистыми и более читабельными.
class TicketsController < ApplicationController ..... def mark_open status_manager do @ticket.update(status: "open") end end def mark_on_hold status_manager do @ticket.update(status: "on_hold") end end def mark_closed status_manager do @ticket.update(status: "closed") end end def status_manager is_status_changed = yield if is_status_changed # request response logic else # error handler end end ...... end
Итак, где я могу использовать блоки?
Status_manager из нашего примера использования приложения для продажи билетов — один из многих вариантов использования, где блоки могут быть полезны.
Например, рассмотрим несколько методов массива Array#each, Array#map, Array#select и т. д. — все эти методы принимают блок.
["procs", "lambdas", "blocks"].each do |concept| puts "Ruby - #{concept}" end
Мы можем сделать то же самое с циклом for или while, буквально с любым итератором. Но это много кода — другими словами, это императивное программирование. Узнать больше об императивной, декларативной и других парадигмах программирования можно здесь.
Полагаю, к настоящему моменту у нас есть ответ на вопрос «Почему блоки?».
Однако с блоками есть одно ограничение. Мы не можем присвоить блок переменной. Допустим, мы хотим реализовать одинаковую функциональность более чем в одном методе. Мы должны отправить блок каждому из методов по отдельности — это приводит нас к следующему разделу «Почему Lambdas?».
Почему лямбды?
Подведение итогов.Лямбда очень похожа на блок и также называется анонимной функцией. Но, в отличие от блоков,лямбды являются объектами и могут быть назначены переменным с помощью специального синтаксиса.
Используя Lambdas и Procs, мы можем сохранить блок в переменной и использовать его повторно. Чтобы быть более точным, блоки бесполезны без вызова метода. Кроме того, лямбда-выражения можно вызывать повторно, не переписывая их каждый раз, в отличие от блоков.
Лямбда-выражения дают больше гибкости, чем методы.
greeter = -> (greeting, name) { greeting + " " + name } greeter.call("Dear", "Ruby Developer") # returns "Dear Ruby Developer"
Проще говоря, лямбда-выражение похоже на любой другой метод, который принимает блок с исключением, назначаемым переменным.
Давайте снова рассмотрим систему продажи билетов, на этот раз обновим наши блоки до одной лямбды.
class TicketsController < ApplicationController ...... def mark_open status_manager("open") end def mark_on_hold status_manager("on_hold") end def mark_closed status_manager("closed") end status_updater = -> (ticket, status) do ticket.update(status: status) end def status_manager(status) is_status_changed = status_updater.call(@ticket, status) ..... end ...... end
Давайте возьмем другой вариант использования и посмотрим, чем могут быть полезны лямбда-выражения.
На этот раз мы предполагаем, что гем для службы API, и у нас есть метод, называемый обратным вызовом, который запускается либо в случае успеха, либо в случае отказа. Нам нужно, чтобы этот метод был настраиваемым и позволял пользователю драгоценного камня определять его функциональность.
# gem code class SomeAPIServiceController ..... def callback(on_success: on_failure:) if success on_success.call(res) else on_failure.call(err) end end ..... end
Мы можем использовать блоки, но мы ограничены использованием только одного блока на вызов метода. Итак, как мы можем передать несколько блоков этому методу обратного вызова?
Подожди, подожди, лямбды, лямбды, дасссс, лямбды на помощь.
success_callback = -> (response) { # Do something with response } failure_callback = -> (error) { # Do something with error } callback(on_success: success_callback, on_failure: failure_callback)
После этого у нас остался последний вопрос в нашем исследовании блоков, лямбда-выражений и процессов — Почему процессы?
Без дальнейших задержек, давайте распутаем процессы.
Почему прокс?
Вывод. Procs, сокращенное название процедуры, очень похоже на лямбда-выражения. Procs — это экземпляры класса Ruby Proc.
Лямбда-выражения — это частный случай Procs и объект proc. Специального класса Lambda нет. Если вы поняли раздел почему Lambdas?, у нас уже есть ответ на вопрос почему Procs?
Кроме того, мы можем использовать Procs вместо lambdas во всех возможных случаях использования, но у lambdas и procs есть свои различия, которые я объяснял в Часть 1: Ответы на все вопросы.
Проки являются полноценными объектами и обладают всеми способностями, которые есть у объектов, в то время как у блоков их нет.
Поскольку лямбда-выражения и процедуры являются объектами, мы можем делать все сумасшедшие вещи, которые могут делать объекты.
proc_object = Proc.new { puts "I am Proc!" } #<Proc:0x00007fc1ccfdcb50@(irb):9> proc_object.call # "I am Proc!"
Ну вот и подошло к концу наше путешествие.
Это слишком чувак 🤓
Давайте подведем итоги и поиграем с нашим милым маленьким 🐶 приятелем….
- Метод нельзя ни передать в качестве аргумента, ни вернуть из другого метода (методы в ruby не являются функциями первого класса).
- Блоки бесполезны без вызовов методов и должны каждый раз переписываться.
- Блоки возвращают контроль программисту.
- Проки — это полноценные Объекты.
- Лямбда-выражения — это частный случай процессов.
- Лямбда-выражения более гибкие, чем методы.
- Использование блоков, лямбда-выражений и процессов. Один метод можно заставить работать в разных аспектах, внедрив некоторый пользовательский код внутри метода извне.
использованная литература
- Любая разница между функцией первого класса и функцией высшего порядка
- Освоение блоков Ruby менее чем за 5 минут
- Полное руководство по блокам, процедурам и лямбда-выражениям
- И много всякой всячины от матушки гуглить.
Я новичок в рубине, поэтому, пожалуйста, дайте мне знать, если я что-то пропустил или неправильно понял. Я с нетерпением жду ваших предложений и отзывов в разделе комментариев ниже.
Я надеюсь, что эта статья помогла вам лучше понять процедуры, лямбда-выражения и блоки. Если вам понравилась эта статья, не забудьте поставить 👏.
Спасибо, что прочитали этот пост. Это Саи Кришна Прасад, самоучка и страстный веб-разработчик. Подписание Bubye….. до следующего раза.