В этом посте я собрал несколько таких деталей в список.
1. Heredoc + Метод
Если у вас есть какие-то текстовые данные, которые вы хотите встроить в программу, вы можете использовать “heredoc”. В результате вы получите строку, например так:
input = <<-IN
ULL
RRDDD
LURDL
IN
Но дополнительно к этому можно использовать пост-процессинг, например разделить текст по словам. Ruby позволяет делать такое:
input = <<-IN.split
ULL
RRDDD
LURDL
IN
А ещё в Ruby 2.3 появился «волнистый» heredoc <<~. Он удаляет все пробелы, использованные для отступов, распространённую проблему использования heredoc для текста.
2. Вызов метода двойным двоеточием
Может, кому-то пригодится.
"abc"::size
# 3
[1,2,3,4,5]::size
# 5
3. Puts с несколькими аргументами
Довольно простая вещь, но часто бывает полезна.
puts 1,2,3
1
2
3
4. Бесконечное взятие по индексу
Сразу пример:
words = ["abc", "foo"]
words[0][0][0][0][0]
Конечно же это работает потому, что [] — это просто метод, и просто возвращает первый символ строки, который тоже является строкой.
5. Деструктуризация аргументов блока
Хотите избавиться от пары локальных переменных? Вам это понравится.
a = [[1,2],[3,4]]
a.each do |(first, last), memo|
# ...
end
Эквивалентно:
a = [[1,2],[3,4]]
a.each do |sub_array, memo|
first, last = sub_array
# ...
end
Зато экономит строчку кода.
6. Специальные глобальные переменные
При использовании регексов со скобками будут определены специальные константы $1 для первой группы, $2 для второй и т.д. Стоит помнить, что они ведут себя не как обычные переменные: имеют другую область видимости (общую для метода и потока), не могут быть переназначены. Список их можно увидеть здесь.
$1 = 'test'
# SyntaxError: (eval):2: Can't set variable $1
7. Оператор присоединения со строкой
Оператор присоединения (<<) работает со строкой не как ожидается, если в качестве аргумента передать число:
"" << 97
# a
Он интерпретирует число как ASCII-код. Штатный способ для того же:
97.chr
# a
8. Символьные литералы
Не уверен, что это на самом деле кому-то пригодится.
?a
"a"
?aa
# Syntax error
Можете писать в комментах, что об этом думаете.
9. Модуль RbConfig
RbConfig — это недокументированный модуль, содержащий кое-какую инфу о вашей конфигурации Ruby. Например, в RbConfig::CONFIG содержатся флаги компиляции интерпретатора, версия, операционная система.
RbConfig.constants
# [:TOPDIR, :DESTDIR, :CONFIG, :MAKEFILE_CONFIG]
RbConfig::CONFIG['host_os']
# "linux-gnu"
RbConfig::CONFIG['ruby_version']
# "2.4.0"
10. Пробелы, везде пробелы!
Между вызываемым методом и получателем можно ставить сколько угодно пробелов.
a = [1,2,3]
a [0]
a .size
a . empty?
Ага, это валидный синтакс в Ruby.
11. Бесконечное наследование констант
String::String::Fixnum::Float
Пытливые умы догадались сразу: все константы верхнего уровня (определённые не внутри какого-либо класса) содержатся в классе Object, и могут быть вызваны любым классом, унаследованным от Object. Чтобы лучше понять, посмотрите на Object.constants в irb.
12. Последовательный оператор присоединения
Оператор << можно объединять в цепочки:
a = []
a << 1 << 2 << 3
# a = [1, 2, 3]
13. BEGIN и END
Два ключевых слова, которые используют довольно редко. Думаю, что они они пришли из мира Perl / Unix, где привычное действие — писать короткие скрипты для обработки вывода других программ. Как оно работает:
puts 123
BEGIN {
puts "Program starting..."
}
Этот код выведет «Program starting...» ПЕРЕД «123». Мой читатель подсказывает, что это бывает полезно, когда нужно изменить путь к RUBYLIB для 'require', потому что будет гарантированно исполнено перед всеми «require». Также бывает полезно для установки $VERBOSE и других констант окружения.
14. ?ЧоЗаХрень
Я даже не знаю как и зачем это появилось в языке, и посоветую аккуратнее с этим обращаться. Мало кто знает об этой фиче, и её очень трудно понять. Но, предупреждён — значит вооружён:
if (condition1)..(condition2)
# do something
end
Идея в том, что если первое условие истинно, то переключается невидимый рычаг, и с этого момента условие будет выполняться, пока второе условие тоже не станет истинным. Пример:
(1..20).each do |i|
puts i if (i == 3)..(i == 15)
end
Оно напечатает все числа с 3 до 15, но если 15 будет пропущено в цикле, то оно так и продолжит печатать.
15. Ключевое слово redo
Ещё одно редко используемое ключевое слово, которое позволяет повторить итерацию в цикле.
10.times do |n|
puts n
redo
end
Только не забывайте ставить его под условие, иначе получится бесконечный цикл. Так что с этой фичей надо быть поосторожнее.
Комментарии (15)
handicraftsman
24.02.2017 16:12Как по мне, всё нормально. Вот только: нафига лезть в незадокументированный модуль? Ведь почти тоже самое есть в глобальных константах
RUBY_whatever
, гдеwhatever
— нужное значение, например —RUBY_VERSION
. Весь их список можно посмотреть в irb.
avost
24.02.2017 17:54+114 из перла, конечно, появилось. Обычный триггер. Триггер переключается в истинное состояние, когда срабатывает первое условие и обратно, когда второе. Полезно когда нужно отпарсить кусок, начинающийся с какого-то "слова" и заканчивающийся другим. Тот же heredoc, например.
Envek
24.02.2017 18:15+1За пункт 2 ваш рубокоп должен больно бить вас по рукам, не делайте так: https://github.com/bbatsov/ruby-style-guide#double-colons
Пункт 14 называется flip-flop и его уже предлагали выпилить его из Ruby, потому что сильно взрывает мозг.am-amotion-city
25.02.2017 12:21+4flip-flop
очень помогает выкусить кусок изEnumerable
в функциональном стиле.
В целом же, список, конечно, писал дилетант: пункт 2 должен звучать как «много где
.
и::
взаимозаменяемы» (дисклеймера «не нужно так делать» не хватает прямо в заголовке), в п. 6 не хватает упоминания о том, что$N
в руби недоварены и работают только в блоках, но не напрямую ("a".gsub(/a/, "#$&".upcase)
внезапно не работает, а"a".gsub(/a/) { "#$&".upcase }
— таки да).
Пункт 11 просто неверен: дело не в константах первого уровня, а в том, как ruby осуществляет их lookup.)
В общем, такие статьи ни писать, ни переводить не надо ни в коем случае.
Envek
26.02.2017 00:34За
$N
Rubocop тоже бьёт линейкой: https://github.com/bbatsov/ruby-style-guide#regular-expressions, возможно, кстати, как раз из-за этой недоваренности, но ещё там проблемы с многопоточностью были, КМК.am-amotion-city
26.02.2017 12:14Нет, с потоками там все в порядке, как раз.
$N
— псевдоглобальные переменные, в каждом потоке свои.
Кстати, перечитал свой вчерашний комментарий и внезапно подумал, что интерполяция глобалов диезом без фигурных скобок (
#$&
из моего примера выше) уж точно заслуживает места в этом списке, но, видать, автор про это просто не знал. Как и про инициализацию лямбды из блока со стека конструкторомProc
:
def m arg Proc.new.(arg) end m(42) { |arg| puts "Argument is: #{arg}" } #? Argument is: 42
А это бывает очень полезно при метапрограммировании вложенных вызовов.
Envek
27.02.2017 00:45А когда нужно писать именно
Proc.new.call(arg)
и не работает простоyield arg
? Или это именно та самая странность-странность (мне мозг подвзорвало, да)?
Ах да, сокращение вызова метода
call
просто до точки — это тоже прикольная и удобная странность, которую вовсю в широкие массы пихает автор Trailblazer'а.am-amotion-city
27.02.2017 08:50когда нужно писать именно
Proc.new.call(arg)
и не работает простоyield arg
?Я немного неудачный пример привел:
call
вместоyield
имеет смысл только в экзотическом случае трейса вызовов блоков (yield
не создает экземплярProc
ни в каком виде). Я имел в виду, что если мы хотим передать блок дальше по стеку вызовов, и блок обязателен, можно не писать везде&cb
в каждой сигнатуре, и проверять в теле методаraise unless block_given?
а просто передавать вглубь&Proc.new
, а если блока нет — оно само бросит эксепшн. Я ни в коем случае не утверждаю, что так нужно делать, да и оверхед там есть, в каждом вложенном методе по новому ?-инстансу создается. Но иногда удобно.
пихает автор
Trailblazer
'аЭто вы в правильном направлении смотрите :)
Dry stack еще посмотрите, может быть понравится. Пиотр строит фреймворк для неглупых людей на очень правильных паттернах, особенно на фоне немного обезумевшего политотно DHH — прямо бальзам на душу.
IDMan
В Руби есть способ не конвертировать dictonary в array при использовании map? Можно конечно
но это довольно некрасивая конструкция. Тогда как
создаст array из значений.
GearHead
не dictionary, а hash.
либо
hash.reduce({}) { ... }
/hash.each_with_object({}) { ... }
либо рельсовые
transform_keys
иtransform_values
Envek
Я думаю, вам нужен такой сниппет:
Кстати, обратите внимание, что метод
<<
меняет переменную, на которой вызывается, а это явно неожиданный результат для того, кто будет читать ваш код (отmap
ожидают создания копии и неизменения содержимого оригинальной переменной). Поэтому лучше использовать интерполяцию (заодно не будет ломаться на нестроковых значениях).Если же вам нужно именно изменить существующий хэш, то просто используйте
each
: