Чего нам ждать от Ruby 2.1?

в 13:37, , рубрики: Без рубрики

Несколько дней назад Константин Хаасе, один из ключевых людей в сообществе 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js