Выбрать главу

10. Блоки и процедурные объекты

Это определённо одна из самых крутых возможностей Ruby. В некоторых других языках тоже есть такие возможности, хотя они могут называться как-нибудь по- другому (например, замыкания), но в большинстве даже более популярных языков, к их стыду, они отсутствуют.

Так что же это за новая крутая возможность? Это способность принимать блок кода (то есть код между do и end), обёртывать его в объект (называемый процедурным объектом или proc по-английски), сохранять его в переменной или передавать его в метод, а затем исполнять код этого блока, когда бы вы ни пожелали (более одного раза, если хотите). Таким образом, блок напоминает настоящий метод за исключением того, что он не привязан ни к какому объекту (он сам является объектом), и вы можете сохранять его или передавать его как параметр подобно тому, как вы это делаете с любым другим объектом. Думаю, настало время привести пример:

toast = Proc

.new do

puts 'Ваше

здоровье! '

end

toast.call

toast.call

toast.call

Ваше здоровье!

Ваше здоровье!

Ваше здоровье!

Итак, я создал объект proc (это название, полагаю, означает сокращение от «procedure», т. е. «процедура», но гораздо более важно, что оно рифмуется с «block»), который содержит блок кода, затем я с помощью call вызвал proc–объект три раза. Как видите, это очень напоминает метод.

На самом деле, это даже более походит на метод, чем в показанном мной примере, так как блоки могут принимать параметры:

doYouLike = Proc.new do |aGoodThing|

puts 'Я *действительно* люблю '+aGoodThing+'!' end

doYouLike.call 'шоколад' doYouLike.call 'рубин'

Я *действительно* люблю шоколад!

Я *действительно* люблю рубин!

Хорошо, вот мы узнали, что из себя представляют блоки и ргос-и читается: «проки»

[Прим. перев.], и как их можно использовать, но в чём же здесь дело? Почему бы просто не использовать методы? Ну потому, что некоторые вещи вы просто не сможете сделать с помощью методов. В частности, вы не можете передавать методы в другие методы (но вы можете передавать в методы процедурные объекты), и методы не могут возвращать другие методы (но они могут возвращать ргос-объекты). Это возможно просто потому, что ргос-и являются объектами, а методы – нет.

(Между прочим, вам это не кажется знакомым? Вот-вот, вы уже видели блоки раньше… когда вы изучали итераторы. Но давайте поговорим об этом ещё чуточку попозже.)

Методы, принимающие процедурные объекты

Когда мы передаём процедурный объект в метод, мы можем управлять тем, как, в каком случае или сколько раз мы вызываем ргос-объект. Например, имеется, скажем, нечто, что мы хотим сделать перед и после выполнения некоторого кода:

def doSelflmportantly someProc

puts 'Всем немедленно ЗАМЕРЕТЬ!

Мне нужно кое-что сделать…'

someProc.call

puts 'Внимание всем, я закончил.

Продолжайте выполнять свои дела.'

end

sayHello = Proc.new do

puts 'привет'

end

sayGoodbye = Proc.new do

puts 'пока'

end

doSelflmportantly sayHello

doSelflmportantly sayGoodbye

Всем немедленно ЗАМЕРЕТЬ! Мне нужно кое-что сделать… привет Внимание всем, я закончил. Продолжайте выполнять свои дела. Всем немедленно ЗАМЕРЕТЬ! Мне нужно кое-что сделать… пока

Внимание всем, я закончил. Продолжайте выполнять свои дела.

Возможно, это не выглядит так уж особенно потрясающим… но это так и есть. :-) В программировании слишком часто имеются строгие требования к тому, что должно быть сделано и когда. Если вы хотите, например, сохранить файл, вам нужно открыть файл, записать туда информацию, которую вы хотите в нём хранить, а затем закрыть файл. Если вы позабудете закрыть файл, могут случиться «Плохие Вещи™. Но каждый раз, когда вы хотите сохранить или загрузить файл, вам требуется делать одно и то же: открывать файл, выполнять то, что вы действительно желаете сделать, затем закрывать файл. Это утомительно и легко забывается. В Ruby сохранение (или загрузка) файлов работает подобно приведённому выше коду, поэтому вам не нужно беспокоиться ни о чём, кроме того, что вы действительно хотите сохранить (или загрузить). (В следующей главе я покажу вам, где разузнать, как делать такие вещи, как сохранение и загрузка файлов.)

Вы также можете написать методы, которые будут определять, сколько раз (или даже при каком условии) вызывать процедурный объект. Вот метод, который будет вызывать переданный ему proc–объект примерно в половине случаев, и ещё один метод, который будет вызывать его дважды:

def maybeDo someProc

# Условный вызов

if rand(2) == 0

someProc.call

end

end

def twiceDo someProc

# Двойной вызов

someProc.call

someProc.call

end

wink = Proc.new do

puts '<nOflMMrHyTb>'

end

glance = Proc.new do

puts ' <B3mHHyTb> '

end

maybeDo wink

maybeDo glance

twiceDo wink

twiceDo glance

<подмигнуть>

<подмигнуть>

<взглянуть>

<взглянуть>

(Если вы перезагрузите эту страницу несколько раз [имеется ввиду страница оригинального учебника – Прим. перев.], то вы увидите другие результаты.) Это самые распространённые применения процедурных объектов, которые дают нам возможность делать такие вещи, которые мы просто не могли бы сделать, используя только методы. Конечно, вы могли бы написать метод, чтобы подмигнуть два раза, но вы не смогли бы написать метод, чтобы просто делать дважды что-нибудь!

Прежде, чем мы продолжим, давайте посмотрим на последний пример. До сих пор все передаваемые процедурные объекты были довольно похожи друг на друга. В этот раз они будут совсем другими, и вы увидите, насколько сильно подобный метод зависит от тех процедурных объектов, что были ему переданы. Наш метод примет обычный объект и процедурный объект, и вызовет процедурный объект с обычным объектом в качестве параметра. Если процедурный объект вернёт false, мы закончим выполнение, иначе мы вызовем процедурный объект с возвращённым объектом. Мы будем продолжать так делать, пока процедурный объект не вернёт false (что ему лучше сделать в конце концов, иначе программа «загнётся»). Этот метод вернёт последнее значение, возвращённое процедурным объектом, не равное false.

def doUntilFalse firstlnput, someProc input = firstlnput output = firstlnput

while output