Книга: Программирование на языке Ruby
1.5.4. Рубизмы и идиомы
1.5.4. Рубизмы и идиомы
Материал в этом разделе во многом пересекается с изложенным выше. Но не задумывайтесь особо, почему мы решили разбить его именно таким образом. Просто многие вещи трудно точно классифицировать и организовать единственно правильным образом. Мы ставили себе задачу представить информацию в удобном для усвоения виде.
Ruby проектировался как непротиворечивый и ортогональный язык. Но вместе с тем это сложный язык, в котором есть свои идиомы и странности. Некоторые из них мы обсудим ниже.
• С помощью ключевого слова alias
можно давать глобальным переменным и методам альтернативные имена (синонимы).
• Пронумерованные глобальные переменные $1
, $2
, $3
и т.д. не могут иметь синонимов.
• Мы не рекомендуем использовать «специальные переменные» $=
, $_
, $/
и им подобные. Иногда они позволяют написать более компактный код, но при этом он не становится более понятным. Поэтому в данной книге мы прибегаем к ним очень редко, что и вам рекомендуем.
• Не путайте операторы диапазона ..
и ...
— первый включает верхнюю границу, второй исключает. Так, диапазон 5..10
включает число 10, а диапазон 5...10
— нет.
• С диапазонами связана одна мелкая деталь, которая может вызвать путаницу. Если дан диапазон m..n
, то метод end
вернет конечную его точку n, равно как и его синоним last
. Но те же методы возвращают значение n и для диапазона m...n
, хотя n не включается в него. Чтобы различить эти две ситуации, предоставляется метод end_excluded?
.
• Не путайте диапазоны с массивами. Следующие два присваивания абсолютно различны:
x = 1..5
x = [1, 2, 3, 4, 5]
Однако есть удобный метод to_a
для преобразования диапазона в массив. (Во многих других типах тоже есть такой метод.)
• Часто бывает необходимо присвоить переменной значение лишь в том случае, когда у нее еще нет никакого значения. Поскольку «неприсвоенная» переменная имеет значение nil
, можно решить эту задачу так: x = x || 5
или сокращенно x ||= 5
. Имейте в виду, что значение false
, а равно и nil
, будет при этом перезаписано.
• В большинстве языков для обмена значений двух переменных нужна дополнительная временная переменная. В Ruby наличие механизма множественного присваивания делает ее излишней: выражение x, y = y, x
обменивает значения x
и y
.
• Четко отличайте класс от экземпляра. Например, у переменной класса @@foobar
областью видимости является весь класс, а переменная экземпляра @foobar
заново создается в каждом объекте класса.
• Аналогично метод класса ассоциирован с тем классом, в котором определен; он не принадлежит никакому конкретному объекту и не может вызываться от имени объекта. При вызове метода класса указывается имя класса, а при вызове метода экземпляра - имя объекта.
• В публикациях, посвященных Ruby, часто для обозначения метода экземпляра применяют решеточную нотацию. Например, мы пишем File.chmod
, чтобы обозначить метод chmod
класса File
, и File#chmod
для обозначения метода экземпляра с таким же именем. Эта нотация не является частью синтаксиса Ruby. Мы старались не пользоваться ей в этой книге.
• В Ruby константы не являются истинно неизменными. Их нельзя изменять в теле методов экземпляра, но из других мест это вполне возможно.
• Ключевое слово yield
пришло из языка CLU и некоторым программистам может быть непонятно. Оно используется внутри итератора, чтобы передать управление блоку, с которым итератор был вызван. В данном случае yield
не означает, что нужно получить результат или вернуть значение. Скорее, речь идет о том, чтобы уступить процессор для работы.
• Составные операторы присваивания +=
, -=
и пр. — это не методы (собственно, это даже не операторы). Это всего лишь «синтаксическая глазурь» или сокращенная форма записи более длинной формы. Поэтому x += y
значит в точности то же самое, что x = x + y
. Если оператор +
перегружен, то оператор +=
«автоматически» учитывает новую семантику.
• Из-за того, как определены составные операторы присваивания, их нельзя использовать для инициализации переменных. Если первое обращение к переменной x
выглядит как x += 1
, возникнет ошибка. Это интуитивно понятно для программистов, если только они не привыкли к языку, в котором переменные автоматически инициализируются нулем или пустым значением.
• Такое поведение можно в некотором смысле обойти. Можно определить операторы для объекта nil
, так что в случае, когда начальное значение переменной равно nil
, мы получим желаемый результат. Так, метод nil.+
, приведенный ниже, позволит инициализировать объект типа string
или Fixnum
, для чего достаточно вернуть аргумент other
. Таким образом, nil + other
будет равно other
.
def nil.+(other)
other
end
Мы привели этот код для иллюстрации возможностей Ruby, но стоит ли поступать так на практике, оставляем на усмотрение читателя.
• Уместно будет напомнить, что Class
— это объект, a Object
— это класс. Мы попытаемся прояснить этот вопрос в следующей главе, а пока просто повторяйте это как мантру.
• Некоторые операторы нельзя перегружать, потому что они встроены в сам язык, а не реализованы в виде методов. К таковым относятся =
, ..
, ...
, and
, or
, not
, &&
, ||
, !
, !=
и !~
. Кроме того, нельзя перегружать составные операторы присваивания (+=
, -=
и т.д.). Это не методы и, пожалуй, даже не вполне операторы.
• Имейте в виду, что хотя оператор присваивания перегружать нельзя, тем не менее возможно написать метод экземпляра с именем fоо=
(тогда станет допустимым предложение x.foo = 5
). Можете рассматривать знак равенства как суффикс.
• Напомним: «голый» оператор разрешения области видимости подразумевает наличие Object
перед собой, то есть ::Foo
— то же самое, что Objеct::Foo
.
• Как уже говорилось, fail
— синоним raise
.
• Напомним, что определения в Ruby исполняются. Вследствие динамической природы языка можно, например, определить два метода совершенно по-разному в зависимости от значения признака, проверяемого во время выполнения.
• Напомним, что конструкция for (for x in а)
на самом деле вызывает итератор each
. Любой класс, в котором такой итератор определен, можно обходить в цикле for
.
• Не забывайте, что метод, определенный на верхнем уровне, добавляется в модуль Kernel
и, следовательно, становится членом класса Object
.
• Методы установки (например, fоо=
) должны вызываться от имени объекта, иначе анализатор решит, что речь идет о присваивании переменной с таким именем.
• Напомним, что ключевое слово retry
можно использовать в итераторах, но не в циклах общего вида. В контексте итератора оно заставляет заново инициализировать все параметры и возобновить текущую итерацию с начала.
• Ключевое слово retry
применяется также при обработке исключений. Не путайте два этих вида использования.
• Метод объекта initialize
всегда является закрытым.
• Когда итератор заканчивается левой фигурной скобкой (или словом end
) и возвращает значение, это значение можно использовать для вызова последующих методов, например:
squares = [1,2,3,4,5].collect do |x| x**2 end.reverse
# squares теперь равно [25,16,9,4,1]
• В конце программы на Ruby часто можно встретить идиому
if $0 == __FILE__
Таким образом проверяется, исполняется ли файл как автономный кусок кода (true
) или как дополнительный, например библиотека (false
). Типичное применение — поместить некую «главную программу» (обычно с тестовым кодом) в конец библиотеки.
• Обычное наследование (порождение подкласса) обозначается символом <
:
class Dog < Animal
# ...
end
Однако для создания синглетного класса (анонимного класса, который расширяет единственный экземпляр) применяется символ <<
:
class << platypus
# ...
end
• При передаче блока итератору есть тонкое различие между фигурными скобками ({}
) и операторными скобками do-end
. Связано оно с приоритетом:
mymethod param1, foobar do ... end
# Здесь do-end связано с mymethod.
mymethod param1, foobar { ... }
# А здесь {} связано с именем foobar, предполагается, что это метод.
• Традиционно в Ruby однострочные блоки заключают в фигурные скобки, а многострочные — в скобки do-end, например:
my_array.each { |x| puts x }
my_array.each do |x|
print x
if x % 2 == 0
puts " четно."
else
puts " нечетно."
end
end
Это необязательно и в некоторых случаях даже нежелательно.
• Помните, что строки (strings) в некотором смысле двулики: их можно рассматривать как последовательность символов или как последовательность строчек (lines). Кому-то покажется удивительным, что итератор each
оперирует строками (здесь под «строкой» понимается группа символов, завершающаяся разделителем записей, который по умолчанию равен символу новой строки). У each
есть синоним each_line
. Если вы хотите перебирать символы, можете воспользоваться итератором each_byte
. Итератор sort
также оперирует строками. Для строк (strings) не существует итератора each_index
из-за возникающей неоднозначности. Действительно, хотим ли мы обрабатывать строку посимвольно или построчно? Все это со временем войдет в привычку.
• Замыкание (closure) запоминает контекст, в котором было создано. Один из способов создать замыкание — использование объекта Proc
. Например:
def power(exponent)
proc {|base| base**exponent}
end
square = power(2)
cube = power(3)
a = square.call(11) # Результат равен 121.
b = square.call(5) # Результат равен 25.
с = cube.call(6) # Результат равен 216.
d = cube.call(8) # Результат равен 512.
Обратите внимание, что замыкание «знает» значение показателя степени, переданное ему в момент создания.
• Однако помните: в замыкании используется переменная, определенная во внешней области видимости (что вполне допустимо). Это свойство может оказаться полезным, но приведем пример неправильного использования:
$exponent = 0
def power
proc {|base| base**$exponent}
end
$exponent = 2
square = power
$exponent = 3
cube = power
a = square.call(11) # Неверно! Результат равен 1331.
b = square.call(5) # Неверно! Результат равен 125.
# Оба результата неверны, поскольку используется ТЕКУЩЕЕ
# значение $exponent. Так было бы даже в том случае, когда
# используется локальная переменная, покинувшая область
# видимости (например, с помощью define_method).
с = cube.call(6) # Результат равен 216.
d = cube.call(8) # Результат равен 512.
• Напоследок рассмотрим несколько искусственный пример. Внутри блока итератора times
создается новый контекст, так что x
— локальная переменная. Переменная closure
уже определена на верхнем уровне, поэтому для блока она не будет локальной.
closure = nil # Определим замыкание, чтобы его имя было известно.
1.times do # Создаем новый контекст.
x = 5 # Переменная x локальная в этом блоке,
closure = Proc.new { puts "В замыкании, x = #{x}" }
end
x = 1
# Определяем x на верхнем уровне.
closure.call # Печатается: В замыкании, x = 5
Обратите внимание, что переменная x, которой присвоено значение 1, — это новая переменная, определенная на верхнем уровне. Она не совпадает с одноименной переменной, определенной внутри блока. Замыкание печатает 5, так как запоминает контекст своего создания, в котором была определена переменная x
со значением 5.
• Переменные с именами, начинающимися с одного символа @
, определенные внутри класса, — это, вообще говоря, переменные экземпляра. Однако если они определены вне любого метода, то становятся переменными экземпляра класса. (Это несколько противоречит общепринятой терминологии ООП, в которой «экземпляр класса» — то же самое, что и «экземпляр>> или «объект».) Пример:
class Myclass
@x = 1 # Переменная экземпляра класса.
@y = 2 # Еще одна.
def mymethod
@x = 3 # Переменная экземпляра.
# Заметим, что в этой точке @y недоступна.
end
end
Переменная экземпляра класса (@y
в предыдущем примере — в действительности атрибут объекта класса Myclass
, являющегося экземпляром класса Class
. (Напомним, что Class
— это объект, a Object
— это класс.) На переменные экземпляра класса нельзя ссылаться из методов экземпляра и, вообще говоря, они не очень полезны.
• attr
, attr_reader
, attr_writer
и attr_accessor
— сокращенная запись для определения методов чтения и установки атрибутов. В качестве аргументов они принимают символы (экземпляры класса Symbol
).
• Присваивание переменной, имя которой содержит оператор разрешения области видимости, недопустимо. Например, Math::Pi = 3.2
— ошибка.
- 6.3.2. Процедура сбора качественных данных
- Глава 2 Табличный редактор Microsoft Excel
- «Плиточные» структуры и квазикристаллы
- 16.13 Формат сообщений MIME
- Как уразуметь свою техническую роль и придерживаться ее
- Отправка запроса
- Build a Run-Time Image for an OS Design
- Как отвечать на письма, полученные вашей организацией
- 5.1.1. Создание трехмерной модели радиатора
- Привязка к процессорам
- Клонирование по сетке ("Copy to Grid")
- Программирование для Linux. Профессиональный подход