Хэл Фултон - Программирование на языке Ruby
- Название:Программирование на языке Ruby
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2007
- Город:Москва
- ISBN:5-94074-357-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Хэл Фултон - Программирование на языке Ruby краткое содержание
Ruby — относительно новый объектно-ориентированный язык, разработанный Юкихиро Мацумото в 1995 году и позаимствовавший некоторые особенности у языков LISP, Smalltalk, Perl, CLU и других. Язык активно развивается и применяется в самых разных областях: от системного администрирования до разработки сложных динамических сайтов.
Книга является полноценным руководством по Ruby — ее можно использовать и как учебник, и как справочник, и как сборник ответов на вопросы типа «как сделать то или иное в Ruby». В ней приведено свыше 400 примеров, разбитых по различным аспектам программирования, и к которым автор дает обстоятельные комментарии.
Издание предназначено для программистов самого широкого круга и самой разной квалификации, желающих научиться качественно и профессионально работать на Ruby.
Программирование на языке Ruby - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Но лично мне такой синтаксис не нравится. Недовольны им и многие другие, включая и самого Маца. Возможно, в будущем эта возможность будет исключена из 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
, имитирующий поведение оператора переключения.
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
Я рекомендую именно такой подход, поскольку в нем все делается открыто, без привлечения «волшебства». Особую актуальность это приобретет, когда оператор переключения будет исключен из языка.
6.2.8. Нестандартные диапазоны
Рассмотрим пример диапазона, состоящего из произвольных объектов. В листинге 6.2 приведен класс для работы с римскими числами.
class Roman
include Comparable
I,IV,V,IX,X,XL,L,XC,C,CD,D,CM,M =
1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000
Values = %w[M CM D CD С XC L XL X IX V IV I]
def Roman.encode(value)
return "" if self == 0
str = ""
Values.each do |letters|
rnum = const_get(letters)
if value >= rnum
return(letters + str=encode(value-rnum))
end
end
str
end
def Roman.decode(rvalue)
sum = 0
letters = rvalue.split('')
letters.each_with_index do |letter,i|
this = const_get(letter)
that = const_get(letters[i+1]) rescue 0
op = that > this ? :- : :+
sum = sum.send(op,this)
end
sum
end
def initialize(value)
case value
when String
@roman = value
@decimal = Roman.decode(@roman)
when Symbol
@roman = value.to_s
@decimal = Roman.decode(@roman)
when Numeric
@decimal = value
@roman = Roman.encode(@decimal)
end
end
def to_i
@decimal
end
def to_s
@roman
end
def succ
Roman.new(@decima1 +1)
end
def <=>(other)
self.to_i <=> other.to_i
end
end
def Roman(val)
Roman.new(val)
end
Сначала несколько слов о самом классе. Его конструктору можно передать строку, символ (представляющий число, записанное римскими цифрами) или Fixnum
(число, записанное обычными арабскими цифрами). Внутри выполняется преобразование и сохраняются обе формы. Имеется вспомогательный метод Roman
, это просто сокращенная запись вызова Roman.new
. Методы класса encode
и decode
занимаются преобразованием из арабской формы в римскую и наоборот.
Для простоты я опустил контроль данных. Кроме того, предполагается, что римские цифры представлены прописными буквами.
Метод to_i
, конечно же, возвращает десятичное значение, a to_s
— число, записанное римскими цифрами. Метод succ
возвращает следующее римское число: например, Roman(:IV).succ
вернет Roman(:V)
.
Интервал:
Закладка: