— Оригинал: 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 Fixnum
6. Код можно выполнять напрямую, без 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) — поменялось