Несколько дней назад Константин Хаасе, один из ключевых людей в сообществе Ruby, опубликовал запись в своём блоге, посвящённую анонсу предварительной версии Ruby 2.1. Изменений между версиями 2.0 и 2.1 накопилось достаточно, чтобы вчитаться в его изложение, и лучше — на русском языке.
NB: разумеется, Ruby 2.1 содержит все замечательные возможности предыдущей версии — 2.0. Изменения предыдущих версий упоминаться не будут.
Механизм уточнений
Известно, что в Ruby 2.0 введён механим уточнений. Реализация данного механизма оказалась достаточно противоречивой, поэтому в версии 2.0 его функциональность была несколько ограничена и помечена как экспериментальная.
Стоит напомнить, что уточнения позволяют применять манки патчи в рамках единственного Ruby-файла:
module Foo refine String do def foo self + "foo" end end end using Foo puts "bar".foo
За пределами данного файла экземпляры класса
String
не будут отвечать на методfoo
.
В новой версии Ruby уточнения не будут являться экспериментальной возможностью. Более того, их можно будет применять не только к области видимости верхнего уровня, но и к отдельным модулям.
module Foo
refine String do
def foo
self + "foo"
end
end
end
module Bar
using Foo
puts "bar".foo
end
Важно иметь в виду, что чрезмерное увлечение уточнениями может привести к написанию достаточно запутанного кода. Разработчики некоторых реализаций Ruby уже заявили, что могут отказаться поддерживать уточнения.
Десятичные литералы
При работе с Ruby можно заметить, что значения с плавающей запятой ведут себя не лучшим образом, когда над ними выполняются вычисления, привычные по работе с десятичными дробями:
irb(main):001:0> 0.1 * 3
=> 0.30000000000000004
Подобное поведение приводит к тому, что большое количество Ruby-разработчиков начинает использовать целые числа, имитируя заданное количество знаков после запятой при представлении результата. Безусловно, такой способ работает хорошо при строго заданном количестве знаков после запятой. В противном случае приходится использовать рациональные дроби — это не очень плохо, но язык не обладает достаточно удобным синтаксисом для работы с ними.
Новая версия Ruby представляет суффикс r
для описания десятичных и рациональных дробей:
irb(main):001:0> 0.1r
=> (1/10)
irb(main):002:0> 0.1r * 3
=> (3/10)
Иммутабельные строки
Если в коде содержится объявление строки, то каждый раз при исполнении содержащей его строки кода Ruby создаёт новый объект класса String
. Это обусловлено мутабельностью строк. В таких случаях символы ведут себя гораздо эффективнее, так как инициализируются всего один раз. Тем не менее, для сравнения символа со строкой нужно провести преобразование строки в символ или символа в строку. Выполнение таких преобразований — рискованная операция, открывающую потенциальную возможность для DoS-атаки, так как символы не освобождаются при сборке мусора, а любое преобразование символа в строку создаёт новую строку.
Единственный способ уберечь себя от негативных последствий в данном случае — хранить и использовать строку как константу:
class Foo
BAR = "bar"
def bar?(input)
input == BAR
end
end
Часто, чтобы избавиться от мутабельности, выполняют заморозку строки. Заморозка объекта предотвращает его изменение со стороны кода на Ruby, однако не даёт никаких прибавок к производительности:
class Foo
BAR = "bar".freeze
def bar?(input)
input == BAR
end
end
Это выглядит достаточно нелепо и громоздко. К счастью, Ruby 2.1 предлагает новый синтаксис для решения данной задачи:
class Foo
def bar?(input)
input == "bar"f
end
end
В приведённом коде будет создан иммутабельный объект класса String
, и где бы он не использовался — он будет инициализирован всего один раз.
Не исключено, что такой синтаксис может показаться странным. Тот же самый фрагмент кода можно переписать эквивалентно:
class Foo
def bar?(input)
input == %q{bar}f
end
end
Вообще, вопрос применения суффикса f
к массивам и хэшам остаётся открытым.
Обязательные ключевые аргументы
Почему-то в анонсе Ruby 2.0 не были упомянуты обязательные ключевые аргументы. Итак, Ruby 2.0 представляет обязательные ключевые аргументы:
def foo(a: 10)
puts a
end
foo(a: 20) # 20
foo # 10
При таком подходе к объявлению методов приходится указывать значения аргументов по-умолчанию. Это не всегда возможно, поэтому Ruby 2.1 позволяет задать обязательные ключевые аргументы:
def foo(a:)
puts a
end
foo(a: 20) # 20
foo # ArgumentError: missing keyword: a
Объявление метода возвращает имя метода
В предыдущих версиях Ruby объявление метода при помощи def
возвращало nil
.
def foo() end # => nil
Теперь это поведение изменилось и имя метода возвращается как символ:
def foo() end # => :foo
Это полезно при метапрограммировании и выполнении подобных трюков. Например, все ли знают, что метод private
может принимать аргументы?
# приватным будет только метод foo
class Foo
def foo
end
private :foo
# метод bar останется незатронутым
def bar
end
end
Теперь, когда def
возвращает имя объявленного метода, можно легко делать методы приватными:
# приватными будут только методы foo и bar
class Foo
private def foo
end
private
def bar
end
def baz
end
end
Удаление лишних байт из строк
Теперь Ruby имеет удобный метод для удаление лишних байт из строк:
some_string.scrub("")
Раньше было сложно добиться одинакового поведения такого метода для всех существующих реализаций Ruby, поэтому также доступна библиотека для этого.
StringScanner поддерживает именованные захваты
Многим нравится класс StringScanner
из стандартной библиотеки языка. В частности, он используется в Rails для разбора шаблонов маршрутов. То же самое будет делать Sinatra 2.0.
В версии 1.9 была добавлена поддержка именованных захватов, однако StringScanner
их не поддерживал:
require 'strscan'
s = StringScanner.new("foo")
s.scan(/(?<bar>.*)/)
puts s[:bar]
В Ruby 2.0 такой код выбросит исключение:
TypeError: no implicit conversion of Symbol into Integer
Зато при запуске на Ruby 2.1 всё будет хорошо:
foo
Работа с сетевыми интерфейсами
Теперь можно получить доступ к сетевыми интерфейсам при помощи метода Socket.getifaddrs
:
require 'socket'
Socket.getifaddrs.each do |i|
puts "#{i.name}: #{i.addr.ip_address}" if i.addr.ip?
end
Пример вывода такой программы:
lo0: fe80::1%lo0
lo0: 127.0.0.1
lo0: ::1
en0: fe80::1240:f3ff:fe7e:594e%en0
en0: 192.168.178.30
en2: fe80::3e07:54ff:fe6f:147a%en2
Быстрая работа с числами для вычислительных задач
Ruby 2.1 ведёт себя быстрее при работе с большими числами благодаря использованию 128-битных целых чисел в качестве внутреннего представления объектов класса Bignum
. Более того, применение GNU Multiple Precision Arithmetic Library даёт дополнительный прирост к производительности.
Изменения в виртуальной машине
Теперь виртуальная машина Ruby наряду с использованием глобального кэша методов выполняет кэширование по месту вызова функции. Про это есть отдельные слайды.
RGenGC
Новая версия Ruby использует новый сборщик мусора на основе поколений. Благодаря этому сборка мусора будет происходить быстрее. До этого использовался консервативный сборщик мусора, работающий по схеме «stop the world — mark — sweep».
На самом деле, старый сборщик никуда не исчез. Такие вещи сложно менять из-за особенностей внутреннего и внешнего программного интерфейса Ruby для языка Си.
Тем не менее, виртуальная машина Ruby 2.1 выполняет классификацию объектов на светлые и тёмные. В зависимости от присвоенного класса определяется поведение сборщика мусора. Имеются операции, которые делают светлый объект тёмным. Например, работа с ним из расширения на языке Си. Такие объекты, как открытые файлы, являются тёмными изначально.
Новый сборщик мусора работает только со светлыми объектами.
Обновление RubyGems
RubyGems получили обновление до версии 2.2.0, которая приносит несколько незначительных усовершенствований.
Ничто не вечно
Не стоит забывать, что недавний релиз является лишь предварительной версией, и всё вышеописанное может измениться.
Автор: eveel