Материал в этом разделе во многом пересекается с изложенным выше. Но не задумывайтесь
особо, почему мы решили разбить его именно таким образом. Просто
многие вещи трудно точно классифицировать и организовать единственно правильным
образом. Мы ставили себе задачу представить информацию в удобном
дня усвоения виде.
Ruby проектировался как непротиворечивый и ортогональный язык. Но вместе
с тем это сложный язык, в котором есть свои идиомы и странности. Некоторые
вз них мы обсудим ниже.
• С помощью ключевого слова alias можно давать глобальным переменным
и методам альтернативные имена (синонимы).
• Пронумерованные глобальные переменные $1, $2, $3 и т.д. не могут иметь
синонимов.
• Мы не рекомендуем использовать «специальные переменные» $=, $_, $/ и
им подобные. Иногда они позволяют написать более компактный код, но
при этом он не становится более понятным. Поэтому в данной книге мы
прибегаем к ним очень редко, что и вам рекомендуем.
• Не путайте операторы диапазона .. и . . . - первый включает верхнюю границу,
второй исключает. Так, диапазон 5.. 10 включает число 10, а диапазон
5... ю - н е т .
• С диапазонами связана одна мелкая деталь, которая может вызвать путаницу.
Если дан диапазон m.. п, то метод end вернет конечную его точку п, равно
как и его синоним last. Но те же методы возвращают значение п и для диапазона
т . . .п, хотя п не включается в него. Чтобы различить эти две ситуации,
предоставляется метод end_excluded?.
• He путайте диапазоны с массивами. Следующие два присваивания абсолютно
различны:
х = 1. .5
х = [1, 2, 3, 4, 5]
Однако есть удобный метод to_a для преобразования диапазона в массив.
(Во многих других типах тоже есть такой метод.)
• Часто бывает необходимо присвоить переменной значение лишь в том случае,
когда у нее еще нет никакого значения. Поскольку «неприсвоенная»
переменная имеет значение nil, можно решить эту задачу так: х = х II 5
или сокращенно х 11=5. Имейте в виду, что значение false, а равно и nil,
будет при этом перезаписано.
• В большинстве языков для обмена значений двух переменных нужна дополнительная
временная переменная. В Ruby наличие механизма множественного
присваивания делает ее излишней: выражение х, у = у, хобменивает
значения х и у.
• Четко отличайте класс от экземпляра. Например, у переменной класса
@@f oobar областью видимости является весь класс, а переменная экземпляра
@foobar заново создается в каждом объекте класса.
• Аналогично метод класса ассоциирован с тем классом, в котором определен;
он не принадлежит никакому конкретному объекту и не может вызываться
от имени объекта. При вызове метода класса указывается имя класса,
а при вызове метода экземпляра - имя объекта.
• В публикациях, посвященных Ruby, часто для обозначения метода экземпляра
применяют решеточную нотацию. Например, мы пишем File.chmod,
чтобы обозначить метод chmod класса File, и Fileftchmod для обозначения
метода экземпляра с таким же именем. Эта нотация не является частью
синтаксиса Ruby. Мы старались не пользоваться ей в этой книге.
• В Ruby константы не являются истинно неизменными. Их нельзя изменять
в теле методов экземпляра, но из других мест это вполне возможно.
• Ключевое слово yield пришло из языка CLU и некоторым программистам
может быть непонятно. Оно используется внутри итератора, чтобы передать
управление блоку, с которым итератор был вызван. В данном случае
yield не означает, что нужно получить результат или вернуть значение.
Скорее, речь идет о том, чтобы уступить процессор для работы.
• Составные операторы присваивания +=, -= и пр. - это не методы (собственно,
это даже не операторы). Это всего лишь «синтаксическая глазурь» или
сокращенная форма записи более длинной формы. Поэтому х += у значит
в точности то же самое, что х = х + у. Если оператор + перегружен, то оператор
+= «автомагически» учитывает новую семантику.
• Из-за того, как определены составные операторы присваивания, их нельзя
использовать для инициализации переменных. Если первое обращение к
переменной х выглядит как х += 1, возникнет ошибка. Это интуитивно понятно для программистов, если только они не привыкли к языку, в котором
переменные автоматически инициализируются нулем или пустым
значением.
Такое поведение можно в некотором смысле обойти. Можно определить
операторы для объекта nil, так что в случае, когда начальное значение переменной
равно nil, мы получим желаемый результат. Так, метод nil. +, приведенный
ниже, позволит инициализировать объект типа string или Fixnum,
для чего достаточно вернуть аргумент other. Таким образом, nil + other будет
равно other.
def nil.+(other)
other
end
Мы привели этот код для иллюстрации возможностей Ruby, но стоит ли
поступать так на практике, оставляем на усмотрение читателя.
Уместно будет напомнить, что class - это объект, a object - это класс. Мы
попытаемся прояснить этот вопрос в следующей главе, а пока просто повторяйте
это как мантру.
Некоторые операторы нельзя перегружать, потому что они встроены в сам
язык, а не реализованы в виде методов. К таковым относятся = , . . , . . . , and,
or, not, &&, 11, !, ! = и !~. Кроме того, нельзя перегружать составные операторы
присваивания (+=, -= и т.д.). Это не методы и, пожалуй, даже не вполне
операторы.
Имейте в виду, что хотя оператор присваивания перегружать нельзя, тем не
менее возможно написать метод экземпляра с именем f оо= (тогда станет допустимым
предложение х. f оо = 5). Можете рассматривать знак равенства
как суффикс.
Напомним: «голый» оператор разрешения области видимости подразумевает
наличие Object перед собой, то есть :.- Foo - то же самое, что Object.-.- Foo.
Как уже говорилось, fail - синоним raise.
Напомним, что определения в Ruby исполняются. Вследствие динамической
природы языка можно, например, определить два метода совершенно
по-разному в зависимости от значения признака, проверяемого во время
выполнения.
Напомним, что конструкция for (for х in а) на самом деле вызывает итератор
each. Любой класс, в котором такой итератор определен, можно обходить
в цикле for.
Не забывайте, что метод, определенный на верхнем уровне, добавляется в
модуль Kernel и, следовательно, становится членом класса Object.
Методы установки (например, f оо=) должны вызываться от имени объекта,
иначе анализатор решит, что речь идет о присваивании переменной с таким
именем.
Напомним, что ключевое слово retry можно использовать в итераторах, ]
не в циклах общего вида. В контексте итератора оно заставляет заново ин^
циализировать все параметры и возобновить текущую итерацию с нача
Ключевое слово retry применяется также при обработке исключений,
путайте два этих вида использования.
Метод объекта initialize всегда является закрытым.
Когда итератор заканчивается левой фигурной скобкой (или словом end)!
возвращает значение, это значение можно использовать для вызова посл^
дующих методов, например:
squares = [1,2,3,4/5].collect do |х| х**2 end.reverse
# squares теперь равно [25,16,9,4,1]
В конце программы на Ruby часто можно встретить идиому
if $0 == FILE
Таким образом проверяется, исполняется ли файл как автономный кус<
кода (true) или как дополнительный, например библиотека (false). Ti
личное применение - поместить некую «главную программу» (обычно
тестовым кодом) в конец библиотеки.
Обычное наследование (порождение подкласса) обозначается символом
class Dog < Animal
# . . .
end
Однако для создания синглетного класса (анонимного класса, который ра<
ширяет единственный экземпляр) применяется символ «:
class << platypus
# ...
end
При передаче блока итератору есть тонкое различие между фигурными скобф
ками ({}) и операторными скобками do-end. Связано оно с приоритетом
mymethod paraml, foobar do ... end
# Здесь do-end связано с mymethod.
mymethod paraml, foobar { ... }
# А здесь {} связано с именем foobar, предполагается, что это метод.
Традиционно в Ruby однострочные блоки заключают в фигурные скобки,
многострочные - в скобки do-end, например:
my_array.each { Ixl 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) запоминает контекст, в котором было создано. Один
из способов создать замыкание - использование объекта Ргос. Например:
def power(exponent)
ргос {Ibasel 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 = О
def power
ргос {Ibasel 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)
d = cube.call(8)
# Результат равен 216.
# Результат равен 512.
Напоследок рассмотрим несколько искусственный пример. Внутри бл
итератора times создается новый контекст, так что х - локальная перемев
ная. Переменная closure уже определена на верхнем уровне, поэтому дщ
блока она не будет локальной.
closure = nil # Определим замыкание, чтобы его имя было известно^
1.times do # Создаем новый контекст.
х = 5 # Переменная х локальная в этом блоке.
closure = Proc.new { puts "В замыкании, х = #{х}" }
end
х = 1
# Определяем х на верхнем уровне.
closure.call # Печатается: В замыкании, х = 5
Обратите внимание, что переменная х, которой присвоено значение 1, - эЛ
новая переменная, определенная на верхнем уровне. Она не совпадает с
ноименной переменной, определенной внутри блока. Замыкание печат»
ет 5, так как запоминает контекст своего создания, в котором была опрВ
делена переменная х со значением 5.
Переменные с именами, начинающимися с одного символа @, определи
ные внутри класса, - это, вообще говоря, переменные экземпляра. Одна!
если они определены вне любого метода, то становятся переменными а
земпляра класса. (Это несколько противоречит общепринятой терминол
гии ООП, в которой «экземпляр класса» - то же самое, что и «экземпляр
или «объект».) Пример:
class Myclass
@х = 1 # Переменная экземпляра класса.
@у = 2 # Еще одна.
def mymethod
@х = 3 # Переменная экземпляра.
# Заметим, что в этой точке @у недоступна,
end
end
Переменная экземпляра класса @у в предыдущем примере - в действительности
атрибут объекта класса Myclass, являющегося экземпляром класса
Class. (Напомним, что Class - это объект, a Object - это класс.) На переменные
экземпляра класса нельзя ссылаться из методов экземпляра и, вообще
говоря, они не очень полезны.
• attr, attr_reader, attr_writer и attr_accessor - сокращенная запись для
определения методов чтения и установки атрибутов. В качестве аргументов
они принимают символы (экземпляры класса Symbol).
• Присваивание переменной, имя которой содержит оператор разрешения
области видимости, недопустимо. Например, Math :: PI = 3.2- ошибка.
