— Оригинал: 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)


  1. printercu
    13.04.2017 15:40

    К 9му:


    2.3.3 :010 > 1073741824000000000.class
     => Fixnum
    2.3.3 :011 > 10737418240000000000.class
     => Bignum

    Что-то поменялось в новом руби?


    1. am-amotion-city
      13.04.2017 15:45

      Не, это от архитектуры зависит, я поправил в заметке:


      main> (1073741823 << 32).class
      #? Fixnum < Integer
      main> (1073741823 << 32 + 1).class
      #? Bignum < Integer


    1. sl_bug
      13.04.2017 15:45

      Нет, это разница между 32-bit и 64-bit. Хотя в новом (2.4) — поменялось

      2.4.1 :001 > 1073741824000000000.class
       => Integer
      2.4.1 :002 > 10737418240000000000.class
       => Integer
      


  1. sshikov
    13.04.2017 19:33

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


    1. am-amotion-city
      13.04.2017 19:37

      Продублировал в начале. Из рекавери моды не дают нормально оформить перевод.


  1. WebMonet
    14.04.2017 14:04

    Напоминает троллейбус из буханки.
    Можно практических примеров применения этих чудесных конструкций?


    1. 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.


      1. shikhalev
        14.04.2017 19:59

        А в чем собственно принципиальная ошибка?


        1. am-amotion-city
          14.04.2017 20:02

          Нарушается контракт. Если я сделаю другой инстанс false (это непросто, но возможно) — неправильный код его проигнорирует, что неверно.


          Не говоря о том, что никто не обещал triple-equal на инстансах FalseClass.


          1. shikhalev
            14.04.2017 20:17

            1. Другой инстанс false или FalseClass? Первое невозможно, второе поломает вообще всё, поскольку false внутри Ruby представлен просто нулем, и все внутренние проверки делаются на ноль (в случае с nil — также на его внутреннее представление — константу), а не на класс. Новый инстанс, если его удастся создать, будет вести себя по другому везде, в частности в if.

            2. Обещал, контракт класса Object.

            И еще: семантически проверка на отсутствие значения и проверка на принадлежность некоему классу — вещи несколько разные, не факт, что их стоит смешивать.


            1. am-amotion-city
              15.04.2017 08:57

              Да, неточно я выразился, пардон. Другой false, еще один инстанс FalseClass. Нет, он ничего не поломает. Я имел в виду, что клиентская библиотека как раз должна полагаться на принадлежность классу, а внутренности пусть себя ведут как им и положено.


              С контрактом класса Object все тоже не так просто. Да, он предоставляет fallback для любых экземпляров чего угодно, но это именно fallback. Мы для своих классов не полагаемся ни на to_s, ни на hash, с чего вдруг мы должны полагаться на case-equal?


              проверка на отсутствие значения и проверка на принадлежность некоему классу — вещи несколько разные, не факт, что их стоит смешивать

              Абсолютно, вы ответили на свой же вопрос гораздо лучше меня. Нет никакого семантического смысла в проверке на тождественность внутреннему представлению nil или false. Это же как указатели сравнивать. В руби утка же священное животное, nil — это то, что крякает как NilClass, а не то, что компилятор си наворотил при компиляции исходного кода, да и в JRuby все не так (хотя, разумеется, фолбек на Object сработает и там).