Статьи

Взгляд на Ruby 2.1

ruby2.1_signal

В этой статье мы рассмотрим новые возможности Ruby 2.1. Впервые он был анонсирован Matz на конференции по Ruby в Барселоне (BaRuCo) 2013. Мы сосредоточимся на Ruby 2.1.0 , который был выпущен в праздничные дни.

Надеюсь, к концу статьи вы будете очень рады Ruby 2.1!

Получение Ruby 2.1

Лучший способ изучить и изучить различные функции — это следовать вместе с примерами. Для этого вам необходимо получить копию последней версии Ruby 2.1:

Если вы находитесь на rvm :

(Вам нужно запустить rvm get head чтобы установить 2.1.0 final)

 $ rvm get head $ rvm install ruby-2.1.0 $ rvm use ruby-2.1.0 

или если вы находитесь на rbenv :

 $ rbenv install 2.1.0 $ rbenv rehash $ rbenv shell 2.1.0 

Обратите внимание, что для пользователей rbenv вы, вероятно, захотите rbenv shell --unset после того, как закончите играть с примерами — если вы не хотите жить на переднем крае. Или вы можете просто закрыть окно терминала.

Давайте удостоверимся, что мы оба используем одну и ту же версию:

 $ ruby -v ruby 2.1.0dev (2013-11-23 trunk 43807) [x86_64-darwin13.0] 

Так что нового?

Вот список функций, которые мы будем решать сегодня. Для более полного списка посмотрите примечания к выпуску Ruby 2.1.0 .

  1. Рациональные числа и литералы комплексных чисел
  2. возвращаемое значение def
  3. Уточнения
  4. Обязательные ключевые слова Аргументы
  5. Уборщик мусора
  6. Отслеживание размещения объектов
  7. Исключение # причина

1. Литералы рациональных чисел и комплексных чисел

В предыдущих версиях Ruby было сложно работать со сложными числами:

 % irb irb(main):001:0> RUBY_VERSION => "2.0.0" irb(main):002:0> Complex(2, 3) => (2+3i) irb(main):003:0> (2+3i) SyntaxError: (irb):3: syntax error, unexpected tIDENTIFIER, expecting ')' (2+3i) ^ from /usr/local/var/rbenv/versions/2.0.0-p247/bin/irb:12:in `<main>' 

Теперь, с введением суффикса i :

 % irb irb(main):001:0> RUBY_VERSION => "2.1.0" irb(main):002:0> (2+3i) => (2+3i) irb(main):003:0> (2+3i) + Complex(5, 4i) => (3+3i) 

Работа с рациональными числами также более приятна. Ранее вам приходилось использовать числа с плавающей запятой, если вы хотите работать с дробями или использовать класс Rational . Суффикс r улучшает ситуацию, предоставляя сокращение для класса Rational .

Поэтому вместо:

 irb(main):001:0> 2/3.0 + 5/4.0 => 1.9166666666666665 

Мы могли бы написать это вместо этого:

 irb(main):002:0> 2/3r + 5/4r => (23/12) 

2. Возвращаемое значение def

В предыдущих версиях Ruby возвращаемое значение определения метода всегда было равно nil :

 % irb irb(main):001:0> RUBY_VERSION => "2.0.0" irb(main):002:0> def foo irb(main):003:1> end => nil 

В Ruby 2.1.0 определения методов возвращают символ:

 irb(main):001:0> RUBY_VERSION => "2.1.0" irb(main):002:0> def foo irb(main):003:1> end => :foo 

Чем это полезно? Пока что один из вариантов использования, с которыми я столкнулся, — это определение private методов. Мне всегда не нравилось, как Ruby определяет частные методы:

 module Foo def public_method end private def a_private_method end end 

Проблема, с которой я сталкиваюсь, заключается в том, что когда классы становятся действительно длинными (несмотря на наши лучшие намерения), иногда легко пропустить это private ключевое слово.

Что интересно, private может принимать символ :

 module Foo def public_method end def some_other_method end private :some_other_method private def a_private_method end end Foo.private_instance_methods => [:some_other_method, :a_private_method] 

Теперь мы можем просто объединить тот факт, что def возвращает символ, а private принимает символ:

 module Foo def public_method end private def some_other_method end private def a_private_method end end Foo.private_instance_methods => [:some_other_method, :a_private_method] 

Если вы заинтересованы в реализации этой новой функции, ознакомьтесь с этой записью в блоге.

3. Уточнения

Уточнения больше не являются экспериментальными в Ruby 2.1. Если вы новичок в доработках, это поможет сравнить его с исправлениями обезьян. В Ruby все классы открыты. Это означает, что мы можем с радостью добавлять методы в существующий класс.

Чтобы оценить хаос, который это может вызвать, давайте переопределим String#count (оригинальное определение здесь ):

 class String def count Float::INFINITY end end 

Если бы вы irb вышеприведенное в irb , каждая строка возвращает Infinity когда count -ed:

 irb(main):001:0> "I <3 Monkey Patching".count => Infinity 

Уточнения предоставляют альтернативный способ охвата объема наших модификаций. Давайте сделаем что-то более полезное:

 module Permalinker refine String do def permalinkify downcase.split.join("-") end end end class Post using Permalinker def initialize(title) @title = title end def permalink @title.permalinkify end end 

Сначала мы определяем модуль Permalinker который refine класс String новым методом. Этот метод реализует передовой алгоритм постоянных ссылок.

Чтобы использовать наше уточнение, мы просто добавляем using Permalinker в наш пример класса Post . После этого мы можем рассматривать, как если бы класс String имеет метод permalinkify .

Давайте посмотрим на это в действии:

 irb(main):002:0> post = Post.new("Refinements are pretty awesome") irb(main):002:0> post.permalink => "refinements-are-pretty-awesome" 

Чтобы доказать, что String#permalinkify существует только в пределах класса Post , давайте попробуем использовать этот метод в другом месте и посмотрим, как код взрывается:

 irb(main):023:0> "Refinements are not globally scoped".permalinkify NoMethodError: undefined method `permalinkify' for "Refinements are not globally scoped":String from (irb):23 from /usr/local/var/rbenv/versions/2.1.0/bin/irb:11:in `<main>' 

4. Обязательные аргументы ключевого слова

В Ruby 2.0 были введены ключевые аргументы:

 def permalinkfiy(str, delimiter: "-") str.downcase.split.join(delimiter) end 

К сожалению, не было способа пометить str как необходимый . Это должно измениться в Ruby 2.1. Чтобы пометить аргумент как необходимый, просто пропустите значение по умолчанию, например:

 def permalinkify(str:, delimiter: "-") str.downcase.split.join(delimiter) end 

Если мы заполним все необходимые аргументы, все будет работать как положено. Однако, если мы что-то опускаем, выдается ArgumentError :

 irb(main):001:0> permalinkify(str: "Required keyword arguments have arrived!", delimiter: "-lol-") => "required-lol-keyword-lol-arguments-lol-have-lol-arrived!" irb(main):002:0> permalinkify(delimiter: "-lol-") ArgumentError: missing keyword: str from (irb):49 from /usr/local/var/rbenv/versions/2.1.0/bin/irb:11:in `<main>' 

5. Сборщик мусора с ограниченным поколением (RGenGC)

В Ruby 2.1 появился новый сборщик мусора, который использует алгоритм сбора мусора поколений .

Основная идея и наблюдение заключается в том, что объекты, которые были созданы совсем недавно, часто умирают молодыми. Следовательно, мы можем разделить объекты на молодые и старые в зависимости от того, выдержат ли они сборку мусора. Таким образом, сборщик мусора может сосредоточиться на освобождении памяти молодого поколения.

Если у нас не хватит памяти даже после сбора мусора молодым поколением (второстепенный сборщик мусора), сборщик мусора перейдет к старому поколению (крупный сборщик мусора).

До Ruby 2.1 сборщик мусора в Ruby работал с консервативным алгоритмом отметки и очистки. В Ruby 2.1 мы все еще используем алгоритм метки и очистки для сбора мусора молодого / старого поколений. Однако, поскольку у нас меньше объектов для отметки, время маркировки сокращается, что приводит к повышению производительности коллектора.

Однако есть предостережения. Чтобы сохранить совместимость с расширениями C, команда ядра Ruby не смогла реализовать «полный» алгоритм сбора мусора поколений. В частности, они не могли реализовать алгоритм перемещения мусора — следовательно, «ограниченный».

Тем не менее, очень приятно видеть, что основная команда Ruby очень серьезно относится к производительности сборки мусора. Для более подробной информации, посмотрите эту прекрасную презентацию Коити Сасада.

6. Исключение # причина

Чарльз Наттер, который реализовал эту функцию , объясняет это лучше всего:

Часто, когда API более низкого уровня вызывает исключение, мы хотели бы повторно вызвать другое исключение, специфичное для нашего API или библиотеки. В настоящее время в Ruby пользователи видят только наше новое исключение; исходное исключение теряется навсегда, если пользователь не решит копаться в нашей библиотеке и регистрировать ее.

Нам нужен способ, чтобы исключение несло «причину» вместе с ним.

Вот пример того, как работает Exception#cause :

 class ExceptionalClass def exceptional_method cause = nil begin raise "Boom!"" # RuntimeError raised rescue => e raise StandardError, "Ka-pow!" end end end begin ExceptionalClass.new.exceptional_method rescue Exception => e puts "Caught Exception: #{e.message} [#{e.class}]" puts "Caused by : #{e.cause.message} [#{e.cause.class}]" end 

Вот что вы получите:

 Caught Exception: Ka-pow! [StandardError] Caused by : Boom! [RuntimeError] 

7. Отслеживание размещения объектов

Если у вас раздутое Ruby-приложение, обычно нетривиальная задача точно определить источник проблемы. В MRI Ruby до сих пор нет инструментов для профилирования, которые могут конкурировать, например, с профилировщиком JRuby .

К счастью, началась работа по отслеживанию выделения объектов для MRI Ruby.

Вот пример:

 require 'objspace' class Post def initialize(title) @title = title end def tags %w(ruby programming code).map do |tag| tag.upcase end end end ObjectSpace.trace_object_allocations_start a = Post.new("title") b = a.tags ObjectSpace.trace_object_allocations_stop puts ObjectSpace.allocation_sourcefile(a) # post.rb puts ObjectSpace.allocation_sourceline(a) # 16 puts ObjectSpace.allocation_class_path(a) # Class puts ObjectSpace.allocation_method_id(a) # new puts ObjectSpace.allocation_sourcefile(b) # post.rb puts ObjectSpace.allocation_sourceline(b) # 9 puts ObjectSpace.allocation_class_path(b) # Array puts ObjectSpace.allocation_method_id(b) # map 

Хотя знание о том, что мы можем получить эту информацию, — это здорово, не сразу понятно, насколько это может быть полезно для вас, разработчика.

Введите камень alloc_stats, написанный Сэмом Роулинсом.

Давайте установим это:

 % gem install allocation_stats Fetching: allocation_stats-0.1.2.gem (100%) Successfully installed allocation_stats-0.1.2 Parsing documentation for allocation_stats-0.1.2 Installing ri documentation for allocation_stats-0.1.2 Done installing documentation for allocation_stats after 0 seconds 1 gem installed 

Вот тот же пример, что и раньше, за исключением того, что на этот раз мы используем allocation_stats :

 require 'allocation_stats' class Post def initialize(title) @title = title end def tags %w(ruby programming code).map do |tag| tag.upcase end end end stats = AllocationStats.trace do post = Post.new("title") post.tags end puts stats.allocations(alias_paths: true).to_text 

Запуск этого приводит к красиво отформатированной таблице:

 sourcefile sourceline class_path method_id memsize class ---------- ---------- ---------- --------- ------- ------ post.rb 10 String upcase 0 String post.rb 10 String upcase 0 String post.rb 10 String upcase 0 String post.rb 9 Array map 0 Array post.rb 9 Post tags 0 Array post.rb 9 Post tags 0 String post.rb 9 Post tags 0 String post.rb 9 Post tags 0 String post.rb 17 Class new 0 Post post.rb 17 0 String 

Сэм выступил с прекрасной презентацией , в которой более подробно рассматривается самоцвет

Счастливых праздников!

Выпуск Ruby 2.1 запланирован на Рождество. Если все пойдет хорошо, это станет прекрасным подарком для всех рубинов. Я особенно рад видеть улучшения в сборщике мусора в Ruby, а также улучшенные возможности профилирования, встроенные в язык, которые позволяют создавать лучшие инструменты профилирования.

Счастливого кодирования и счастливых праздников!