В этой статье мы рассмотрим новые возможности 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 .
- Рациональные числа и литералы комплексных чисел
- возвращаемое значение
def
- Уточнения
- Обязательные ключевые слова Аргументы
- Уборщик мусора
- Отслеживание размещения объектов
- Исключение # причина
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, а также улучшенные возможности профилирования, встроенные в язык, которые позволяют создавать лучшие инструменты профилирования.
Счастливого кодирования и счастливых праздников!