Книга: Программирование на языке 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 — ошибка.

Оглавление книги


Генерация: 4.725. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз