— Оригинал: Jesus Castello
1. Глубокое копирование
Когда вы копируете объект, который содержит другие объекты, например Array, в копии оказываются ссылки на оригинальные объекты.
Вот, смотрите:
food = %w( bread milk orange )
food.map(&:object_id) # [35401044, 35401020, 35400996]
food.clone.map(&:object_id) # [35401044, 35401020, 35400996]Используя же класс Marshal, который в обычной жизни предназначен для сериализации, можно сделать «глубокую копию», то есть скопировать и внутренние объекты тоже.
def deep_copy(obj)
Marshal.load(Marshal.dump(obj))
end
deep_copy(food).map(&:object_id) # [42975648, 42975624, 42975612]2. Различные способы вызова лямбд
my_lambda = -> { puts 'Hello' }
my_lambda.call
my_lambda[]
my_lambda.()
my_lambda.===Если это возможно, впрочем, вы должны использовать первый вариант (call), потому что именно про него знают все и вы внезапно не удивите людей, читающих ваш код.
3. Создание предзаполненного массива
Фабрика класса Array может принимать аргумент и блок, что позволяет создавать массив с несколькими (n) элементами. По умолчанию эти элементы — экземпляры класса NilClass (их значение равно nil), но если вы передаете в вызов блок, значения заполняются оттуда.
Array.new(10) { rand 300 }Этот код создаст массив из десяти элементов, каждый из которых — случайное число в диапазоне от 0 до 299.
4. true, false и nil — объекты
true.class # TrueClass
false.class # FalseClass
nil.class # NilClassСуществует единственный экземпляр каждого из этих классов, и создать другой вам не удастся, даже если вы очень захотите.
Это паттерн «singleton» в действии.
5. lambda строго относится к количеству аргументов, Proc — нет
my_lambda = ->(a, b) { a + b }
my_proc = Proc.new { |a, b| a + b }
my_lambda.call(2)
#? ArgumentError: wrong number of arguments (1 for 2)
my_proc.call(2)
#? TypeError: nil can't be coerced into Fixnum6. Код можно выполнять напрямую, без irb, pry, или файла
Командная строка руби принимает некоторые интересные опции, которыми можно пользоваться.
Например, флаг -e можно напрямую передать кусок кода, который будет выполнен.
ruby -e '5.times { puts "Fun with Ruby" }'Про остальные флаги можно узнать, запустив интерпретатор с ключом -h.
7. Собственный mini-irb одной строкой
Хотели когда-нибудь узнать, как работает irb? Ну, вот мегаупрощенная ее версия.
Помните, что означает аббревиатура «REPL»: Read-Eval-Print Loop (прочитать-выполнить-напечатать-повторить).
ruby -n -e 'p eval($_)'У этой версии нет приглашения командной строки, но ничто не может остановить вас: давайте, попробуйте, наберите какой-нибудь код на руби.
"A" * 5
"AAAAA"Это работает, потому что ключ «-n» делает вот что:
-n assume 'while gets(); ... end' loop around your scriptНу а $_ — просто глобальная переменная, которая хранит:
The last input line of string by gets or readline.8. Разморозить (unfreeze) объект (опасно!)
В самом руби нет метода, который позволит разморозить ранее замороженный (frozen) объект, но используя класс Fiddle из стандартной библиотеки, вы можете проникнуть во внутренний мир руби и сделать невозможное возможным.
require 'fiddle'
str = 'water'.freeze
str.frozen? # true
memory_address = str.object_id * 2
Fiddle::Pointer.new(memory_address)[1] &= ~8
str.frozen? # falseНе пытайтесь повторить это дома или в школе!
9. Объекты с особенной индивидуальностью (identity)
У всех объектов в руби Ruby есть идентификатор id — целое число, которое вы можете посмотреть, вызвав метод object_id. У некоторых объектов этот идентификатор фиксирован: у чисел, наследников Fixnum, а также у true, false и nil.
false.object_id # 0
true.object_id # 2
nil.object_id # 4
1.object_id # 3
2.object_id # 5Для Fixnumов верна следующая формула: (number * 2) + 1.
Бонус: Самое большое число, представимое Fixnum — это 1073741823 (верно для 32-разрядной архитектуры), дальше уже идут объекты класса Bignum.
10. Предотвращение слишком длинного вывода в irb или pry
Если вы работаете в irb и хотите избежать вывода на экран какого-нибудь громадного куска данных (например, гигантского массива, или строки) — просто добавьте точку с запятой ; в конец вашего кода.
require 'rest-client'
# ?
RestClient.get('blackbytes.info');А теперь попробуйте еще разок без ; и найдите одно отличие
11. Метод caller для получения полного стека текущего вызова
Вот пример:
def foo
bar
end
def bar
puts caller
end
fooВыведет:
-:3:in 'foo'
-:10:in '<main>'Для того, чтобы просто получить имя метода, воспользуйтесь __method__ или __callee__.
Прим. переводчика: разница между __method__ и __callee__ в том, что будет выведено для алиасов: __method__ всегда вернет имя настоящего метода, в то время, как __callee__ вернет имя алиаса:
main> def m; puts [__callee__, __method__].inspect; end
main> alias :a, :m
main> m
#? [:m, :m]
main> a
#? [:a, :m]Во всех минорных версиях Ruby2.3 эта функциональность сломана, оба метода возвращают [:m, :m]. Будьте осторожны!
Бонус! Конвертирование любого значения в boolean
!!(1) # true
!!(nil) # falseЭто все.
— Оригинал: Jesus Castello
Комментарии (11)

sshikov
13.04.2017 19:33Это перевод. А, сорри, да, просто ссылка немного не там.

am-amotion-city
13.04.2017 19:37Продублировал в начале. Из рекавери моды не дают нормально оформить перевод.

WebMonet
14.04.2017 14:04Напоминает троллейбус из буханки.
Можно практических примеров применения этих чудесных конструкций?
am-amotion-city
14.04.2017 16:49Это перевод, вообще-то. Но если вам неясно, зачем может потребоваться глубокая копия хеша, или почему иногда важно уметь создать лямбду, которая нестрого относится к количеству переданных параметров, или почему в
caseправильно писать так:
case o when NilClass then puts "nil" when FalseClass then puts "false"
а не так (это принципиальная ошибка):
case o when nil then puts "nil" when false then puts "nil"
— то вам, наверное, это все и не нужно. Продолжайте программировать на
php.
shikhalev
14.04.2017 19:59А в чем собственно принципиальная ошибка?

am-amotion-city
14.04.2017 20:02Нарушается контракт. Если я сделаю другой инстанс
false(это непросто, но возможно) — неправильный код его проигнорирует, что неверно.
Не говоря о том, что никто не обещал triple-equal на инстансах
FalseClass.
shikhalev
14.04.2017 20:171. Другой инстанс
falseилиFalseClass? Первое невозможно, второе поломает вообще всё, посколькуfalseвнутри Ruby представлен просто нулем, и все внутренние проверки делаются на ноль (в случае сnil— также на его внутреннее представление — константу), а не на класс. Новый инстанс, если его удастся создать, будет вести себя по другому везде, в частности вif.
2. Обещал, контракт классаObject.
И еще: семантически проверка на отсутствие значения и проверка на принадлежность некоему классу — вещи несколько разные, не факт, что их стоит смешивать.
am-amotion-city
15.04.2017 08:57Да, неточно я выразился, пардон. Другой
false, еще один инстансFalseClass. Нет, он ничего не поломает. Я имел в виду, что клиентская библиотека как раз должна полагаться на принадлежность классу, а внутренности пусть себя ведут как им и положено.
С контрактом класса
Objectвсе тоже не так просто. Да, он предоставляет fallback для любых экземпляров чего угодно, но это именно fallback. Мы для своих классов не полагаемся ни наto_s, ни наhash, с чего вдруг мы должны полагаться на case-equal?
проверка на отсутствие значения и проверка на принадлежность некоему классу — вещи несколько разные, не факт, что их стоит смешивать
Абсолютно, вы ответили на свой же вопрос гораздо лучше меня. Нет никакого семантического смысла в проверке на тождественность внутреннему представлению
nilилиfalse. Это же как указатели сравнивать. В руби утка же священное животное,nil— это то, что крякает какNilClass, а не то, что компилятор си наворотил при компиляции исходного кода, да и вJRubyвсе не так (хотя, разумеется, фолбек наObjectсработает и там).
printercu
К 9му:
Что-то поменялось в новом руби?
am-amotion-city
Не, это от архитектуры зависит, я поправил в заметке:
sl_bug
Нет, это разница между 32-bit и 64-bit. Хотя в новом (2.4) — поменялось