input = output output = someProc.call input end
input
end
buildArrayOfSquares = Proc.new do |array| # Создание массива квадратов чисел
lastNumber = array.last if lastNumber <= 0 false else
array.pop # Уберём последнее число…
array.push lastNumber*lastNumber # …и заменим его на его квадрат…
array.push lastNumber–1 # …за которым идет предыдущее
число.
end
end
alwaysFalse = Proc.new do |justIgnoreMe| false end
puts doUntilFalse([5], buildArrayOfSquares).inspect
puts doUntilFalse ('Я пишу это в 3 часа утра; кто–то меня вырубил!', alwaysFalse)
[25, 16, 9, 4, 1, 0]
Я пишу это в 3 часа утра; кто-то меня вырубил!
Хорошо, признаю, что это был довольно странный пример. Но он показывает, насколько по-разному ведёт себя наш метод, когда ему передают совсем разные процедурные объекты.
Метод inspect во многом похож на to_s за исключением того, что возвращаемая им строка – это попытка показать код на Ruby для создания объекта, который вы ему передали. Здесь он показывает нам весь массив, возвращённый при нашем первом вызове метода doUntilFalse. Вы, должно быть, также заметили, что мы сами никогда не возводили в квадрат этот 0 в конце массива, но поскольку 0 в квадрате всегда равен 0, нам это и не нужно было делать. А так как alwaysFalse, как вы знаете, возвращает всегда false, метод doUntilFalse ничего не делал, когда мы вызвали его во второй раз; он просто вернул то, что ему было передано.
Методы, возвращающие процедурные объекты
Ещё одна из крутых возможностей, которые можно делать с процедурными объектами, это то, что их можно создавать в методах, а затем возвращать их. Это делает возможным разнообразные сумасшедшие, но мощные программистские штучки (с впечатляющими названиями наподобие ленивое вычисление, бесконечные структуры данных и карринг). Но дело в том, что я почти никогда не использовал это на практике, а также не припомню, чтобы видел, как кто-либо применял это в своём коде. Думаю, это не такого рода вещи, которые обычно нужно делать на Ruby, а, может быть, Ruby просто подталкивает вас находить другие решения – не знаю. В любом случае, я только кратко коснусь этого.
В этом примере метод compose принимает два процедурных объекта и возвращает новый процедурный объект, который, будучи вызван, вызывает первый процедурный объект и передаёт его результат во второй.
def compose proc1, proc2
Proc.new do |x|
proc2.call(proc1.call(x))
end
end
squareIt = Proc.new do |x|
x * x
end
doublelt = Proc.new do |x|
x + x
end
doubleThenSquare = compose doublelt,
squareIt
squareThenDouble = compose squareIt,
doublelt
puts doubleThenSquare.call(5)
puts squareThenDouble.call(5)
>
100
50
Обратите внимание, что вызов proel должен быть внутри скобок при вызове proc2, чтобы он был выполнен первым.
Передача блоков (не proc–объектов) в методы
Ну, хорошо, этот подход представляет чисто академический интерес, к тому же применять его несколько затруднительно. В основном трудность состоит в том, что здесь вам приходится выполнить три шага (определить метод, создать процедурный объект и вызвать метод с процедурным объектом); тогда как есть ощущение, что должно быть только два (определить метод и передать блок непосредственно в этот метод, совсем не используя процедурный объект), поскольку в большинстве случаев вы не хотите использовать процедурный объект / блок после того, как вы передали его в метод. Что ж, да будет вам известно, что в Ruby всё это уже сделано за нас! Фактически, вы уже делали это каждый раз, когда использовали итераторы.
Сначала я быстро покажу вам пример, а затем мы обсудим его.
class Array
def eachEven(&wasABlock_nowAProc)
isEven = true # Мы начинаем с «true», т.к. массив начинается с 0, а он чётный.
self.each do |object| if isEven
wasABlock_nowAProc.call object end
isEven = (not isEven) # Переключиться с чётного на нечётное или наоборот.
end
end
end
['яблоками', 'гнилыми яблоками', 'вишней', 'дурианом'].eachEven do |fruit| puts
'Мммм! Я так люблю пирожки с '+fruit+', а вы?' end
# Помните, что мы берём элементы массива с чётными номерами,
# все из которых оказываются нечётными числами; это
# просто потому, что мне захотелось создать подобные трудности.
[1, 2, 3, 4, 5].eachEven do |oddBall|
puts oddBall.to_s+' – НЕ чётное число!' end
Мммм!
Я так люблю пирожи
с яблоками,
а вы?
Мммм!
Я так люблю пирожи
с вишней, а
вы?
– Н И
чётное число!
ЕН – 3
чётное число!
5 – НЕ
чётное число!
Итак, всё, что мы должны сделать, чтобы передать блок в метод eachEven, это «прилепить» блок после метода. Подобным же образом вы можете передать блок в любой метод, хотя многие методы просто проигнорируют блок. Чтобы заставить ваш метод не игнорировать блок, а взять его и превратить его в процедурный объект, нужно поместить имя процедурного объекта в конце списка параметров вашего метода и поставить перед ним амперсанд (&). Конечно, это немного мудрёно, но не слишком, и вам придётся сделать это только один раз (когда вы описываете метод). А затем вы можете использовать этот метод снова и снова точно так же, как и встроенные методы, принимающие блоки такие, как each и times. (Помните, 5.times do…?)
Если для вас это слишком запутанно, просто помните, что должен сделать eachEven: вызвать переданный ему блок для каждого чётного элемента в массиве. После того, как однажды вы написали метод и убедились, что он работает, вам уже не нужно думать о том, что в действительности делается «под капотом» («какой блок и когда вызывается??»). На самом деле, именно поэтому мы пишем подобные методы: чтобы нам никогда не приходилось снова думать о том, как они работают. Мы просто используем их.
Помню, один раз я захотел сделать, чтобы можно было измерять, сколько времени выполняются различные секции программы. (Это также известно как профилирование программного кода.) И я написал метод, который засекает время перед исполнением кода, затем выполняет его, в конце снова засекает время и вычисляет разницу. Сейчас я не могу найти этот метод, но мне он и не нужен; он, возможно, выглядел примерно так:
def profile descriptionOfBlock, &block # Описание блока и сам блок startTime = Time.now
block.call
duration = Time.now – startTime
puts descriptionOfBlock+': '+duration.to_s+' сек.' end
profile '25000 удваиваний' do number = 1
25000.times do
number = number + number end
puts number.to_s.length.to_s+' цифр' # Да, это число цифр в таком ГИГАНТСКОМ числе.
end
profile 'сосчитать до миллиона' do number = 0