Понимание процедур, лямбда-выражений и блоков в 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 ​​не являются функциями первого класса).
  • Блоки бесполезны без вызовов методов и должны каждый раз переписываться.
  • Блоки возвращают контроль программисту.
  • Проки — это полноценные Объекты.
  • Лямбда-выражения — это частный случай процессов.
  • Лямбда-выражения более гибкие, чем методы.
  • Использование блоков, лямбда-выражений и процессов. Один метод можно заставить работать в разных аспектах, внедрив некоторый пользовательский код внутри метода извне.

использованная литература

Я новичок в рубине, поэтому, пожалуйста, дайте мне знать, если я что-то пропустил или неправильно понял. Я с нетерпением жду ваших предложений и отзывов в разделе комментариев ниже.

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

Спасибо, что прочитали этот пост. Это Саи Кришна Прасад, самоучка и страстный веб-разработчик. Подписание Bubye….. до следующего раза.