Недавно я влюбился в простой шаблон функции высшего порядка в JavaScript. Это выглядит примерно так:

Учитывая массив,

// .js
const oneToTen = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

это:

oneToTen.map(number => number * 3)
  //=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

то же самое, что и это:

const timesThree = number => number * 3
oneToTen.map(timesThree) 
  //=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

Этот пример корпоративного уровня не является сверхсложным, но вы можете себе представить, насколько он может быть полезен для удобочитаемости и разделения проблем по мере увеличения длины строки блоков. Тот же шаблон работает для других методов, таких как Array.prototype.reduce и Array.prototype.filter.

После некоторых путешествий в JavaScriptopolis и возвращения в Rubyland я обнаружил, что хочу использовать аналогичный шаблон. Поскольку методы не являются объектами первого класса в Ruby, это не так просто. Но я вспомнил свои ранние дни Ruby, когда наткнулся на идеи процедур и лямбда-выражений, и что они были связаны с выполнением блоков кода.

В то время я избегал их, поскольку тогда они казались слишком сложными, но на этот раз они казались более приемлемыми. Для контекста:

Объекты Proc - это блоки кода, которые были привязаны к набору локальных переменных. После привязки код может вызываться в разных контекстах и ​​по-прежнему обращаться к этим переменным .

Для меня это означает, что объекты proc - это способ Ruby передавать функции как объекты. Лямбды - это особые виды процедур. Эти различия хорошо задокументированы здесь, но выходят за рамки этой статьи.

Интересный факт: proc - это сокращение от procedure.

Вот что я придумал, используя лямбды, для следования приведенному выше шаблону JavaScript:

Учитывая массив,

# .rb
one_to_ten = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

это:

one_to_ten.map { |number| number * 3 }
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

то же самое, что и это:

def times_three
  lambda do |number|
    number * 3
  end
end
one_to_ten.map(&times_three)
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

который также совпадает с этим (синтаксис «stabby lambda»):

def times_three
  -> number { number * 3 }
end
one_to_ten.map(&times_three)
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

и лямбда также может быть сохранена в простой переменной ol ’:

times_three = -> number { number * 3 }
one_to_ten.map(&times_three)
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]

Прекрасный. Наша функция .map вызывает лямбда в очень декларативном стиле, а лямбда заботится об императивной части.

А как насчет аргументов?

Представьте себе другой корпоративный случай, когда вам нужна функция, которая сообщает вам, все ли слова больше заданного числового аргумента:

words = ["apple", "okay", "not", "yes", "forgettable"]
def longer_than(number)
  -> word { word.length > number }
end
words.all?(&longer_than(5))
  #=> false
words.all?(&longer_than(2))
  #=> true

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

words.select(&longer_than(3))
  #=> ["apple", "okay", "forgettable"]
words.reject(&longer_than(3))
  #=> ["not", "yes"]
words.one?(&longer_than(5))
  #=> true

Еще раз times_three :

Чтобы сделать нашу times_three лямбду еще более дистиллированной и динамичной, мы можем сделать это:

def times(factor)
  -> number { number * factor }
end
one_to_ten.map(&times(3))
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
one_to_ten.map(&times(5))
  #=>[ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 ]

Еще лучше:

def operate(sign, arg)
  -> number { number.send(sign, arg) }
end
one_to_ten.map(&operate(:*, 3))
  #=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
one_to_ten.map(&operate(:-, 6))
  #=> [ -5, -4, -3, -2, -1, 0, 1, 2, 3, 4 ]

Ура! Функции высшего порядка, которые принимают лямбда (или специальную процедуру) в качестве аргумента, и этот аргумент может принимать аргументы. Секундочку ...

… Хорошо, я вернулся. Мне пришлось победно вскинуть руки за то, что я написал такой прекрасно читаемый код.

Вывод

Учитывая эти примеры, я надеюсь, что вы сочтете лямбды такими же разумными, желательными и КРАСИВЫМИ, как и я. Вы можете деконструировать свои перечислимые вызовы, чтобы они были более декларативными, оставив лямбда для обработки фактической реализации. Так что в следующий раз, когда вы обнаружите, что набираете do |var| или { |var| ... вашего перечислимого типа, добавьте туда лямбду.