Новые функции, которые можно попробовать в предстоящем выпуске ruby-2.6.0-preview3

Внимание, мы переехали! Если вы хотите и дальше следить за последними техническими новостями Square, посетите наш новый дом https://developer.squareup.com/blog

По мере приближения Рождества мы будем видеть все больше и больше функций для Ruby 2.6. Некоторые из них мало обсуждались, в том числе Enumerable и Enumerator, и я нашел эти изменения особенно интересными. Давайте взглянем.

Перечислимый # to_h

Если вы раньше запускали Ruby, вы, вероятно, знакомы с Hash[] и его использованием следующим образом:

hash = { a: 1, b: 2 }
Hash[hash.map { |k, v| [k, v + 1] }]
=> { a: 2, b: 3 }

В дни 2.x мы начали видеть Enumerable#to_h:

hash = { a: 1, b: 2 }
hash.map { |k, v| [k, v + 1] }.to_h
=> { a: 2, b: 3 }

Это, безусловно, был более приемлемый вариант, но что, если бы он пошел еще дальше? В Ruby 2.6+ to_h возьмет блок, который работает как map:

hash = { a: 1, b: 2 }
hash.to_h { |k, v| [k, v + 1] }
=> { a: 2, b: 3 }

Поскольку это метод для Enumerable, мы можем использовать его и для других вещей, таких как диапазоны и массивы:

(1..5).to_h { |n| [n, n**2] }
=> {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}

По общему признанию, это не так явно, как комбинация map и to_h, но это дает нам еще один шаг к сокращению хеш-кода преобразования.

Хэш # слияние

В предыдущих версиях Ruby, чтобы объединить несколько хешей вместе, вам приходилось выполнять их по одному:

h1.merge(h2).merge(h3).merge(h4)

… Или потенциально использовать двойной знак, чтобы рассматривать их все как один большой встроенный хеш:

h1.merge(**h2, **h3, **h4)

… Или для тех из нас, кто особенно любит сокращение, для этого есть даже аромат Ruby!

[h1, h2, h3, h4].reduce(&:merge)

Теперь все они работают, но с переменным числом аргументов, вот и все. Ruby 2.6 вводит вариативные аргументы для Hash#merge:

h1.merge(h2, h3, h4)

Обратите внимание, что это также работает с Hash#merge! и Hash#update.

Перечислитель :: ArithmeticSequence

Это непросто - попробуйте сказать это в пять раз быстрее. Идите вперед и откройте REPL, если вы еще этого не сделали. Мы собираемся попробовать несколько вещей.

До версии 2.6, если вы попробуете это:

(1..100).step(3) == (1..100).step(3)

Ты собирался получить хороший старый false прямо сейчас. Хотя они выглядят одинаково, правда? Почему они не равны?

Оказалось, что основная команда согласилась, поэтому они создали концепцию ArithmeticSequence, чтобы позаботиться именно об этом:



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

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

(1..10).step(3) == 1.step(by: 3, to: 10)
1.step(by: 2, to: 10)
=> [1, 3, 5, 7, 9]
(1..10).step(3)
=> [1, 4, 7]

Мы также получили новое применение для оператора по модулю:

(1..100).step(3) == (1..100) % 3

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

Все вместе сейчас!

Это не была бы статья без невероятно надуманного и откровенно ужасающего примера кода. Что ж, не бойтесь! У нас есть именно то, что нужно, и это наш старый друг FizzBuzz:

def fizzbuzz(rules, range)
  mappings = rules.map { |step, value|
    (range % step).to_h { |n| [n, value || n] }
  }
  fizzbuzz_map = {}.merge(*mappings) { |_, old_value, new_value|
    is_numeric = [old_value, new_value].any?(Numeric)
    is_numeric ? new_value : old_value + new_value
  }
  fizzbuzz_map.values
end
rules = {1 => nil, 3 => 'Fizz', 5 => 'Buzz'}
range = (1..100)
puts fizzbuzz(rules, range)

Что мы здесь делаем, так это определяем набор правил, используя хэш, указывающий скорость шага (сколько чисел пропустить с момента последнего числа в диапазоне) и значение.

Поскольку числа, кратные 3 и 5, имеют специальные отображения, мы говорим об этом в наших правилах. Это означает, что когда мы добираемся до создания значений нашего шага, оно не будет передаваться для фактического числа в операторе value || n.

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

Одна из интересных особенностей merge заключается в том, что он может принимать блок, который представляет ключ, и пересекающиеся значения в этом ключе в двух разных хэшах в качестве старого и нового значений. Используя это, мы можем решить, какое значение как бы «оставить».

Мы всегда предполагаем, что строковые правила имеют приоритет над числовыми, и если строка уже существует, мы должны где-то там найти число, делящееся на 15.

Это, безусловно, неэффективно, но тем не менее интересное исследование некоторых новых функций.

Подведение итогов

В версии 2.6 появится много интересных новых функций, многие из которых еще даже не вышли. Это всего лишь небольшой обзор, и мы с нетерпением ждем остальных.

Спасибо моему товарищу Square Shannon «havenwood» Skipper за изучение последних коммитов Ruby, запланированных для ruby-2.6.0-preview3, вместе со мной. Мы используем много Ruby здесь, в Square - в наших SDK Square Connect Ruby, во многих проектах с открытым исходным кодом, которые мы поддерживаем, и в многом другом. Мы с нетерпением ждем выхода Ruby 2.6!