С выходом Ruby 2.0, который будет выпущен 24 февраля, как раз в 20-ю годовщину первого дебюта Ruby, я решил написать эту статью, чтобы дать вам краткое изложение некоторых из самых интересных изменений. И если вы хотите поэкспериментировать с этой версией до выхода официального релиза, вы можете сделать это, следуя инструкциям в этой статье.
Установка RC1
Ruby 2.0 отказался от использования syck в пользу psych , а YAML теперь полностью зависит от libyaml . Это означает, что мы должны установить эту библиотеку перед установкой Ruby 2.0.
В любой системе, основанной на * nix, мы можем установить ее, загрузив исходный пакет и собрав его вручную:
$ wget http://pyyaml.org/download/libyaml/yaml-0.1.4.tar.gz $ tar xzvf yaml-0.1.4.tar.gz $ cd yaml-0.1.4 $ ./configure $ make $ make install
Или, если вы используете Mac, вы можете установить его с помощью homebrew :
$ brew update $ brew install libyaml
После установки libyaml мы можем установить RC1 с помощью rvm :
$ rvm install ruby-2.0.0-rc1
Далее нам нужно указать rvm использовать эту новую версию Ruby:
$ rvm use ruby-2.0.0-rc1
Отлично, теперь мы готовы погрузиться в изменения.
изменения
Изменения, внесенные в 2.0, довольно обширны , но в этой статье я сосредоточусь в основном на следующем:
- Уточнения
- Ключевое слово Аргументы
- Модуль # перед именем
- Перечислимых # ленивым
- Языковые изменения
Уточнения (экспериментальные)
Уточнения заменяют небезопасные исправления обезьян, предоставляя лучший, безопасный и изолированный способ исправления кода. Традиционно, когда патч применяется, он изменяет объект глобально — нравится вам это или нет. С уточнениями вы можете ограничить исправление обезьян определенными областями.
Давайте посмотрим на конкретный пример исправления обезьян. Скажем, мы хотели расширить класс String и добавить метод bang, который добавляет восклицательный знак после заданной строки. Мы бы сделали что-то вроде этого:
class String def bang "#{self}!" end end
Это изменение теперь глобально. Это означает, что любая строка, которая вызывает .bang
будет вести себя так:
> "hello".bang #=> "hello!"
Чтобы предотвратить это глобальное изменение объема, было предложено усовершенствование . Он работает, используя два новых метода: Module#refine
и main.using
. Во-первых, это блок, который позволяет выполнять локальные исправления для обезьян. И последнее импортирует уточнения в текущий файл или строку eval, чтобы их можно было использовать в других местах.
Взяв наш предыдущий пример, мы можем безопасно расширить класс String, используя уточнения:
module StringBang refine String do def bang "#{self}!" end end end
Теперь, если мы попытаемся вызвать .bang
для любой строки, произойдет сбой:
> "hello".bang #=> NoMethodError: undefined method `bang' for "":String
Это связано с тем, что изменение класса String содержится в модуле StringBang . После того, как мы импортируем это уточнение с using
ключевого слова using
, оно будет работать как положено:
> using StringBang > "hello".bang #=> "hello!"
ПРЕДУПРЕЖДЕНИЕ. Эта функция все еще является экспериментальной, и ее поведение может измениться в будущих версиях Ruby. У Чарльза Наттера из JRuby есть отличное объяснение проблем, связанных с этим.
Ключевое слово Аргументы
Эта функция, также называемая именованными параметрами , очень полезна, поскольку позволяет объявить метод для получения аргументов ключевого слова. Это используется многими языками и, наконец, интегрировано в Ruby.
По-старому, если вы хотите принять аргументы ключевого слова, вам придется подделать его, получив хеш-аргумент в методе:
def config(opts={}); end
И если бы у вас были значения по умолчанию, вы бы объединили предоставленные пользователем аргументы поверх ваших значений по умолчанию:
def config(opts={}) defaults = {enabled: true, timeout: 300} opts = defaults.merge(opts) end
Это конечно работает, но это взлом, и я уверен, что мы все использовали это так или иначе.
Забудьте этот старый способ и скажите «привет» истинным ключевым аргументам в Ruby 2.0. Используя тот же пример, что и выше, вот как мы можем определить наш метод config
новым способом:
def config(enabled: true, timeout: 300) [enabled, timeout] end
Теперь давайте посмотрим, как мы можем вызвать этот метод:
> config() #no args #=> [true, 300] > config(enabled: false) #only enabled #=> [false, 300] > config(timeout: 20) #only timeout #=> [true, 20] > config(timeout: 10, enabled: false) #inverse order #=> [false, 10]
Расширяя метод config
далее, теперь мы собираемся принять два дополнительных аргумента: value
, обязательное значение; и other
, необязательный хеш.
def config(value, enabled: true, timeout: 300, **other) [value, enabled, timeout, **other] end
И вот разные, что мы можем взаимодействовать этим методом:
> config() #no args #=> ArgumentError: wrong number of arguments (0 for 1) > config(1) #required value #=> [1, true, 300, {}] > config(1, other: false) #required value, optional hash #=> [1, true, 300, {:other=>false}] > config(1, timeout: 10) #required value, timeout #=> [1, true, 10, {}] > config(1, timeout: 10, other: false) #required value, timeout, optional hash #=> [1, true, 10, {:other=>false}] > config(1, other: false, another: true, timeout: 10, enabled: false) #inverse order #=> [1, false, 10, {:other=>false, :another=>true}]
Модуль # перед именем
Это похоже на Module#include
за исключением того, что он настроен на «наложение констант, методов и переменных модуля» ( Source ) предшествующего модуля. Чтобы лучше понять эту концепцию, давайте посмотрим, как работает Module#include
:
module Foo def baz 'foo-baz' end end class Bar include Foo def baz 'bar-baz' end end
Здесь мы объявляем модуль и класс, и каждый из них содержит объявление метода baz
. Когда мы вызываем метод baz
в классе Bar , метод, объявленный в модуле Foo, игнорируется:
> Bar.new.baz #=> "bar-baz"
С Module#prepend
оно является обратным; объявление в модуле перезаписывает объявление в классе. Переписав приведенный выше пример для использования Module#prepend
, вот новый код:
module Foo def baz 'foo-baz' end end class Bar prepend Foo def baz 'bar-baz' end end
И при вызове метода baz
в классе Bar метод в модуле Foo действительно вызывается:
> Bar.new.baz #=> "foo-baz"
Перечислимых # ленивым
Цитируется непосредственно из документации:
Возвращает ленивый перечислитель, методы которого map / collect, flat map / collect concat, select / find all, reject, grep, zip, take, #take while, drop, #drop_ while и циклически перечисляют значения только по мере необходимости , Однако, если блок отдается zip или циклу, значения перечисляются немедленно ».
Теперь, когда у нас есть понимание его концепции, давайте рассмотрим пример:
> ary = [1,2,3,4,5].select{|n| n > 2} #=> [3, 4, 5] > ary = [1,2,3,4,5].lazy.select{|n| n > 2} #=> #:select> > ary.force #=> [3, 4, 5]
В первой части, без использования lazy
метода, новый массив возвращается сразу после того, как метод select
завершает оценку. Во второй части, когда используется lazy
метод, возвращается ленивый перечислитель, и код не оценивается, пока мы не вызовем force
(или to_a
).
Языковые изменения
Теперь вы можете использовать % i и % I для создания списка символов:
> %i{this is a list of symbols} #=> [:this, :is, :a, :list, :of, :symbols]
Вывод
В этой статье представлены некоторые из наиболее обсуждаемых изменений, внесенных в Ruby 2.0, и я лично не могу дождаться выхода официального релиза.
Если вы все еще используете Ruby 1.8x, настоятельно рекомендуется обновить его до 1.9.3 как можно скорее, так как он скоро будет устаревшим и более не поддерживается. Что касается совместимости между Ruby 2.0 и 1.9.3, он считается полностью совместимым.