6.2.7. Оператор переключения
6.2.7. Оператор переключения
Диапазон в составе условия обрабатывается особым образом. В этом случае .. называется оператором переключения (flip-flop operator), поскольку это, по существу, переключатель, который сохраняет свое состояние.
Такой прием, позаимствованный из языка Perl, бывает полезен. Но понять, как он работает, довольно трудно.
Представьте себе исходный текст программы на Ruby, в который встроена документация, ограниченная маркерами =begin и =end. Как бы вы подошли к задаче отыскания и вывода этих и только этих фрагментов? (Состояние переключается между «внутри раздела» и «вне раздела», отсюда и понятие переключения.) Решение, хотя интуитивно и не очевидное, дает следующий код:
loop do
break if eof?
line = gets
puts line if (line=~/=begin/)..(line=~/=end/)
end
«Волшебство» объясняется принципом работы оператора переключения.
Во-первых, надо осознать, что «диапазон» сохраняет свое состояние, хотя оно и скрыто. Когда становится истинным условие, заданное в качестве левой границы, сам диапазон принимает значение true. Он сохраняет это состояние до тех пор пока не станет истинным условие на правой границе, и в этот момент состояние переключается в false.
Такое поведение полезно во многих случаях, в частности для разбора HTML-документов или конфигурационных файлов, разбитых на разделы, выбора диапазонов элементов из списков и т.д.
Но лично мне такой синтаксис не нравится. Недовольны им и многие другие, включая и самого Маца. Возможно, в будущем эта возможность будет исключена из Ruby. Однако я покажу удобный способ реализовать ту же функциональность по-другому.
Что меня не устраивает в операторе переключения? В контексте предыдущего примера рассмотрим строку, начинающуюся с маркера =begin. Напомним, что оператор =~ не возвращает true или false, как можно было бы ожидать; он возвращает начальную позицию найденного соответствия (Fixnum) или nil, если соответствие не найдено. Следовательно, при вычислении выражений для строк, попадающих и не попадающих в диапазон, мы получаем 0 и nil соответственно.
Однако при попытке сконструировать диапазон от 0 до nil возникает ошибка, поскольку такой диапазон не имеет смысла:
range = 0..nil # Ошибка!
Далее, напомню, что в Ruby только false и nil дают значение «ложь» — все остальные объекты в логическом контексте вычисляются как «истина». А значит, следуя общей идеологии диапазон не должен вычисляться как «ложь».
puts "hello" if x..y
# Печатается "hello" для любого допустимого диапазона x..y.
Но предположим, что мы сохранили эти значения в переменных, а потом из них сконструировали диапазон. Все перестанет работать, так как проверка всегда дает true.
loop do
break if eof?
line = gets
start = line=~/=begin/
stop = line=~/=end/
puts line if start..stop
end
А что если сам диапазон поместить в переменную? Тоже не получится — проверка снова дает true.
loop do
break if eof?
line = gets
range = (line=~/=begin/)..(line=~/=end/)
puts line if range
end
Чтобы понять, в чем дело, нужно осознать, что весь диапазон (включая обе границы) вычисляется на каждой итерации цикла, но с учетом внутреннего состояния. Поэтому оператор переключения — вообще не настоящий диапазон. Тот факт, что он выглядит похожим на диапазон, хотя по сути таковым не является, многие считают «злом».
И наконец, задумаемся о границах в операторе переключения. Они вычисляются каждый раз, но результат вычисления нельзя сохранить в переменной и затем просто подставить ее. В некотором смысле граничные точки оказываются похожи на объекты proc. Это не значения, а исполняемый код. Тот факт, что нечто, выглядящее как обычное выражение, на самом деле представляет собой proc, тоже не вызывает восторга.
И несмотря на все вышесказанное, функциональность-то полезная!.. Можно ли написать класс, который инкапсулирует ее, но при этом не будет таким «магическим»? Можно и даже не очень трудно. В листинге 6.1 приведен простой класс Transition, имитирующий поведение оператора переключения.
Листинг 6.1. Класс Transition
class Transition
А, В = :А, :В
T, F = true, false
# state,p1,p2 => newstate, result
Table = {[A,F,F]=>[A,F], [B,F,F]=>[B,T],
[A,T,F]=>[B,T], [B,T,F]=>[B,T],
[A,F,T]=>[A,F], [B,F,T]=>[A,T],
[A,T,T]=>[A,T], [B,T,T]=>[A,T]}
def initialize(proc1, proc2)
@state = A
@proc1, @proc2 = proc1, proc2
check?
end
def check?
p1 = @proc1.call ? T : F
p2 = @proc2.call ? T : F
@state, result = *Table[[@state,p1,p2]]
return result
end
end
В классе Transition для управления переходами применяется простой конечной автомат. Он инициализируется парой объектов proc (теми же, что для оператора переключения). Мы утратили небольшое удобство: все переменные (например, line), которые используются внутри этих объектов, должны уже находиться в области видимости. Зато теперь у нас есть решение, свободное от «магии», и все выражения ведут себя так, как в любом другом контексте Ruby.
Вот слегка измененный вариант того же подхода. Здесь метод initialize принимает proc и два произвольных выражения:
def initialize(var,flag1,flag2)
@state = A
@proc1 = proc { flag1 === var.call }
@proc2 = proc { flag2 === var.call }
check?
end
Оператор ветвящегося равенства проверяет соотношение между границами и переменной. Переменная обернута в объект proc, потому что мы передаем это значение только один раз, но хотим иметь возможность вычислять его повторно. Поскольку proc — замыкание, это не составляет проблемы. Вот как используется новая версия:
line = nil
trans = Transition.new(proc {line}, /=begin/, /=end/)
loop do break if eof? line = gets
puts line if trans.check?
end
Я рекомендую именно такой подход, поскольку в нем все делается открыто, без привлечения «волшебства». Особую актуальность это приобретет, когда оператор переключения будет исключен из языка.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Настройка переключения раскладки клавиатуры и служб текстового ввода
Настройка переключения раскладки клавиатуры и служб текстового ввода Далее необходимо проверить (и, при необходимости – изменить) настройки языков и служб текстового ввода. Для перехода в соответствующий режим необходимо на вкладке Языки и клавиатуры нажать кнопку
8.3. Программа автоматического переключения раскладки клавиатуры Punto Switcher
8.3. Программа автоматического переключения раскладки клавиатуры Punto Switcher Еще одним средством, которое поможет значительно ускорить набор, являются программы автоматического переключения клавиатурных раскладок. Если в вашем тексте встречаются и русские, и английские
R.6.5.1 Оператор while
R.6.5.1 Оператор while В операторе while вложенный оператор выполняется до тех пор, пока значение выражения не станет равным нулю. Проверка происходит перед каждым выполнением оператора.Выражение должно быть арифметического типа, или типа указателя, или типа класс, для которого
Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete
Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete Операторы new и delete с размещением встречаются в C++ не слишком часто, поэтому в том, что вы с ними не знакомы, нет ничего страшного. Вспомните (правила 16 и 17), что когда вы пишете такое
Оператор do while
Оператор do while Ключевые слова: do, whileОбщие замечания: Оператор do while создает цикл, который повторяется до тех пор, пока выражение, проверяющее условие, не станет ложным, или нулем. Оператор do while является циклом с постусловием; после прохождения цикла принимается решение,
1. Оператор Select – базовый оператор языка структурированных запросов
1. Оператор Select – базовый оператор языка структурированных запросов Центральное место в языке структурированных запросов SQL занимает оператор Select, с помощью которого реализуется самая востребованная операция при работе с базами данных – запросы.Оператор Select
15.8.2. Оператор размещения new() и оператор delete()
15.8.2. Оператор размещения new() и оператор delete() Оператор-член new() может быть перегружен при условии, что все объявления имеют разные списки параметров. Первый параметр должен иметь тип size_t:class Screen {public:void *operator new( size_t );void *operator new( size_t, Screen * );// ...};Остальные параметры
1.4.7 Оператор while
1.4.7 Оператор while Рассмотрим копирование строки, когда заданы указатель p на ее первый символ и указатель q на целевую строку. По соглашению строка оканчивается символом с целым значением 0.while (p != 0) (* *q = *p; // скопировать символ q = q+1; p = p+1; *) *q = 0; // завершающий символ 0 скопирован
1.4.8 Оператор for
1.4.8 Оператор for Рассмотрим копирование десяти элементов одного вектора в другой:for (int i=0; i«10; i++) q[i]=p[i];Это эквивалентно int i = 0; while (i«10) (* q[i] = p[i]; i++; *) но более удобочитаемо, поскольку вся информация, управляющая циклом, локализована. При применении операции ++ к целой
9.4 Оператор While
9.4 Оператор While Оператор while имеет видwhile ( выражение ) операторВыполнение подоператора повторяется, пока значение выржения остается ненулевым. Проверка выполняется перед каждым выполнением оператора. Выражение обрабатывается как в услоном операторе
7.3. Программы автоматического переключения раскладки клавиатуры (Punto Switcher)
7.3. Программы автоматического переключения раскладки клавиатуры (Punto Switcher) Еще одним средством, которое поможет значительно ускорить набор, являются программы автоматического переключения клавиатурных раскладок. Если в вашем тексте встречаются и русские, и английские
Эффекты переключения между окнами
Эффекты переключения между окнами При включенном стиле Windows Aero для переключения между окнами можно использовать сочетание клавиш Windows+Tab, с помощью которого реализуется эффект трехмерного пролистывания открытых окон (рис. 2.11). Этот эффект также называется Flip 3D, или