Итак, приступим.
Создаем хэш из массива
Проще простого. Ставим команду Hash перед любым массивом и получаем готовые пары ключ/значение:
Hash['key1', 'value1', 'key2', 'value2']
# => {"key1"=>"value1", "key2"=>"value2"}
Lambda как ->
Возможность проставлять лямбду при помощи -> появилась сравнительно недавно, будем пробовать:
a = -> { 1 + 1 }
a.call
# => 2
a = -> (v) { v + 1 }
a.call(2)
# => 3
Двойная звездочка (**)
Как вам такой метод:
def my_method(a, *b, **c)
return a, b, c
end
а — это обычный аргумент. *b примет все аргументы после «a» и выведет их массивом, а вот **c принимает только параметры в формате ключ/значение, после чего отдаст нам хэш. Посмотрим примеры:
Один аргумент:
my_method(1)
# => [1, [], {}]
Набор аргументов:
my_method(1, 2, 3, 4)
# => [1, [2, 3, 4], {}]
Набор аргументов + пары ключ/значение
my_method(1, 2, 3, 4, a: 1, b: 2)
# => [1, [2, 3, 4], {:a=>1, :b=>2}]
По-моему, круто.
Обращаемся с переменной и с массивом одинаково
Иногда (лишь иногда) у вас может возникнуть желание запустить на объекте какой-либо метод без проверки его типа. То бишь обращаться с массивом так же как, скажем, с обычной переменной. В таких случаях можно пойти двумя путями — использовать [*something] или Array(something).
Давайте попробуем. Назначим две переменные: число и массив чисел
stuff = 1
stuff_arr = [1, 2, 3]
Используя [*] мы можем одинаково успешно итерировать по обеим переменным:
[*stuff].each { |s| s }
[*stuff_arr].each { |s| s }
Идентично:
Array(stuff).each { |s| s }
Array(stuff_arr).each { |s| s }
||=
Отличный ключ к сокращению количества строк нашего кода — использование ||=
Важно понять, что этот оператор работает так:
a || a = b # Верно
А не так:
a = a || b # Неверно!
Этот оператор прекрасно подходит для выполнения математических операций:
def total
@total ||= (1..100000000).to_a.inject(:+)
end
Теперь мы можем использовать total в других методах, но посчитается он только один раз, что отразится на производительности нашего приложения.
Обязательные хэш-параметры
Совсем новая фича вторых рубей. Вместо того, чтобы определять метод с указанием хэша в качестве принимаемого аргумента:
def my_method({})
end
теперь мы можем четко определить ключи, которые мы ждем на входе. Более того, мы можем определить их значения!
В данном примере a и b являются обязательными ключами:
def my_method(a:, b:, c: 'default')
return a, b, c
end
Можем попробовать отправить в метод только «а» и нарваться на ошибку:
my_method(a: 1)
# => ArgumentError: missing keyword: b
Так как мы указали значение по умолчанию для «с», нам достаточно предоставить методу ключи «а» и «b»:
my_method(a: 1, b: 2)
# => [1, 2, "default"]
Или же можем отправить все три:
my_method(a: 1, b: 2, c: 3)
# => [1, 2, 3]
Можем быть более лаконичны:
hash = { a: 1, b: 2, c: 3 }
my_method(hash)
# => [1, 2, 3]
Генерируем алфавит или цепочку чисел при помощи range
Трюк достаточно старый, но вдруг кто-то не в курсе.
('a'..'z').to_a
# => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
(1..10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Tap
Tap — это отличный метод, способный улучшить читаемость нашего кода. Допустим, у нас есть класс:
class User
attr_accessor :a, :b, :c
end
Теперь, допустим, нам захотелось создать нового пользователя, с атрибутами. Можно сделать это так:
def my_method
o = User.new
o.a = 1
o.b = 2
o.c = 3
o
end
А можно использовать tap:
def my_method
User.new.tap do |o|
o.a = 1
o.b = 2
o.c = 3
end
end
Комментарии (34)
grossws
02.05.2015 23:20+7Возможность проставлять лямбду при помощи -> появилась сравнительно недавно
В 1.9 она уже есть. Недавно?! xD
GearHead
03.05.2015 05:35В следующий раз лучше выбирайте пост для перевода. Если человек ведёт блог, это ещё не значит, что он профессионал языка.
Кроме выше означенных проблем, вот ещё. Назовите разницу между
a || a = b
и
a = a || b
С логической точки зрения её нет.modernstyle Автор
03.05.2015 10:20+1Во втором случае нет смысла переназначать «a» если мы уже имеем ее в распоряжении.
mktums
03.05.2015 11:46-1а вот **c принимет только параметры в формате ключ/значение, после чего отдаст нам хэш.
О божечки, они изобрели **kwargs :DEnvek
03.05.2015 15:33И правильно сделали, этого не хватало, были всякие костыли типа extract_arguments в начале многих методов. Кстати говоря, именно поэтому 5-е рельсы активно перепиливают на использование **kwargs везде (следовательно минимальная версия Ruby там будет 2.2).
grossws
03.05.2015 16:57Оно же с 2.0 появилось? Например, здесь пишут, что это так: magazine.rubyist.net/?Ruby200SpecialEn-kwarg
Это не считая того, что синтаксический сахар для последнего аргумента-хэша и в 1.9 был. Менее удобный, правда, так как значения аргументов по умолчанию выставлялись в теле функции и проверка наличия избыточных аргументов делалась вручную и довольно редко.
flamefork
03.05.2015 13:39Важно понять, что этот оператор работает не так:
a || a = b
и не так:a = a || b
:
irb(main):001:0> b = :caveat => :caveat irb(main):002:0> a1 ||= b => :caveat irb(main):003:0> a2 || a2 = b NameError: undefined local variable or method `a2' for main:Object
Tonkonozhenko
03.05.2015 17:07Так все таки, как он работает?
Loriowar
03.05.2015 17:23Работает как и говорили выше
a = b unless a
Поэтому много проблем влечёт ||=, так как забывают люди постоянно про то, что с Bool данную конструкцию использовать нельзя.
irb(main):001:0> a = 42 => 42 irb(main):002:0> a ||= 77 => 42 irb(main):003:0> a = false => false irb(main):004:0> a ||= true => true irb(main):005:0> a => true
Вроде как и понятно почему, но когда идёт несколько идентичных методов или присваиваний и среди них забываешь про Bool становится очень обидно за убитое на дебаг время.Tonkonozhenko
04.05.2015 03:24За bool спасибо, не задумывался об этом
Но вот
a||=b
не совсем
a = b unless a
потому что
2.2.0 :001 > a = :symb => :symb 2.2.0 :002 > b ||= a => :symb 2.2.0 :003 > b = a unless b => nil 2.2.0 :004 > b => :symb
а скорее
if defined?(b) && b then b else b = a end
flamefork
04.05.2015 11:22Собсно я еще над этим подумал, и получается, что как раз
a = a || b
так оно и работает :) То есть так, как в посте помечено коментарием «Неверно». Поправьте, в чем неправ.
Drakula2k
04.05.2015 16:16Стоит отметить, что ||= нужно применять с осторожностью для вычислений, которые могут вернуть nil, в таком случае результат не будет закеширован. Для решения этой проблемы есть гемы типа memoist.
achempion
04.05.2015 22:52код почему-то не подсвечивается
def products
return @cached[:result] if @cached.is_a?(Hash)
#…
# нужные операции
#…
@cached = {result: result}
end
GrigoryPerepechko
По-моему с Tap читаемость только ухудшилась
mannaro
Тут разумнее привести такой код:
Вместо Количество строк одна против 3. А также лишняя переменная.
Но, как по мне, то читать легче без tap. Тут надо смотреть, чего мы хотим: мало строк или хорошая читаемость.
shemerey
Если для ActiveRecod то можно и без tap
тоже самое на create, update
mannaro
Ну, это для рельс.
grossws
Видел такой в куче библиотек относящих к рельсам чуть менее, чем никак.
Паттерн обычно выглядит следующим образом:
Renius
точнее для ActiveRecord
и для всех объектов принимающих блок
GearHead
то, что вы привели в качестве примера, не пройдёт ни один стайлгайд.
.tap используется в основном для возвращения значения внутри метода:
вместо
оно не сокращает количество строк, а улучшает читаемость
kovyl
Так а где улучшение читаемости-то? То же ж самое абсолютно.
matiouchkine
Да ни разу он не для улучшения читаемости просто. Бывает, что нужно что-то выполнить на промежуточном результате:
def my_f
User.new.tap { |u| puts “New user: #{u}” }
end
Напечатали лог и вернули вновь созданного юзера из функции. Вместо:
def my_f
user = User.new
puts “New user: #{user}”
user
end
kovyl
Вот тут, согласен, tap вполне приятно смотрится.
Dreyk
удивительное совпадение в том, что tap как раз и был введен прежде всего для таких манипуляций: то есть, для чтения, а не для мутации. Мутация — это практически побочный эффект
GearHead
нет лишней переменной.
Alerticus
зато есть лишний блок, лол