nashbridges.me - Проки и лямбды









Search Preview

Проки и лямбды

nashbridges.me
Процедурный объект в Ruby, его область применения. Чем отличаются Proc.new и lambda.
.me > nashbridges.me

SEO audit: Content analysis

Language Error! No language localisation is found.
Title Проки и лямбды
Text / HTML ratio 52 %
Frame Excellent! The website does not use iFrame solutions.
Flash Excellent! The website does not have any flash contents.
Keywords cloud в > и => = не end с блок что Ruby его метод метода на к мы блока это
Keywords consistency
Keyword Content Title Description Headings
в 127
> 87
и 78
=> 72
= 49
не 49
Headings
H1 H2 H3 H4 H5 H6
2 9 11 0 0 0
Images We found 0 images on this web page.

SEO Keywords (Single)

Keyword Occurrence Density
в 127 6.35 %
> 87 4.35 %
и 78 3.90 %
=> 72 3.60 %
= 49 2.45 %
не 49 2.45 %
end 43 2.15 %
с 37 1.85 %
блок 36 1.80 %
что 33 1.65 %
Ruby 32 1.60 %
его 31 1.55 %
метод 29 1.45 %
метода 28 1.40 %
на 27 1.35 %
к 26 1.30 %
26 1.30 %
мы 25 1.25 %
блока 25 1.25 %
это 24 1.20 %

SEO Keywords (Two Word)

Keyword Occurrence Density
=> nil 21 1.05 %
001 > 18 0.90 %
в Ruby 14 0.70 %
end => 13 0.65 %
002 > 13 0.65 %
да да 12 0.60 %
в качестве 9 0.45 %
= lambda 8 0.40 %
процедурный объект 8 0.40 %
может быть 7 0.35 %
007 > 7 0.35 %
> def 7 0.35 %
004 > 7 0.35 %
процедурного объекта 6 0.30 %
on Rails 6 0.30 %
blocks = 6 0.30 %
жива => 6 0.30 %
cat_status = 6 0.30 %
003 > 6 0.30 %
Лондоне сейчас 6 0.30 %

SEO Keywords (Three Word)

Keyword Occurrence Density Possible Spam
end => nil 9 0.45 % No
Ruby on Rails 6 0.30 % No
В Лондоне сейчас 6 0.30 % No
да да да 6 0.30 % No
В этой пицце 5 0.25 % No
А кошкато жива 5 0.25 % No
в качестве блока 5 0.25 % No
жива => nil 5 0.25 % No
этой пицце есть 5 0.25 % No
Вы подходите к 4 0.20 % No
geturl blocks = 4 0.20 % No
def geturl blocks 4 0.20 % No
кошкато жива => 4 0.20 % No
> link_tocall 'httpкоторыйчас' 4 0.20 % No
006?> end => 4 0.20 % No
к казино и 4 0.20 % No
Для ее приготовления 4 0.20 % No
казино и даете 4 0.20 % No
ее приготовления нужны 4 0.20 % No
подходите к казино 4 0.20 % No

SEO Keywords (Four Word)

Keyword Occurrence Density Possible Spam
В этой пицце есть 5 0.25 % No
Для ее приготовления нужны 4 0.20 % No
кошкато жива => nil 4 0.20 % No
def geturl blocks = 4 0.20 % No
подходите к казино и 4 0.20 % No
к казино и даете 4 0.20 % No
А кошкато жива => 4 0.20 % No
Вы подходите к казино 4 0.20 % No
> link_tocall 'httpкоторыйчас' 'Который 3 0.15 % No
вышибала Вы подходите к 3 0.15 % No
link_tocall 'httpкоторыйчас' 'Который час?' 3 0.15 % No
в Ruby on Rails 3 0.15 % No
ArgumentError wrong number of 3 0.15 % No
wrong number of arguments 3 0.15 % No
принимает в качестве аргумента 3 0.15 % No
boxcall А кошкато жива 3 0.15 % No
Кошка разбирает взрывное устройство 3 0.15 % No
> boxcall А кошкато 3 0.15 % No
В Лондоне сейчас london_time 3 0.15 % No
001 > cat_status = 2 0.10 % No

Internal links in - nashbridges.me

все теги
Теги, которыми отмечены статьи
зачем и для кого
О блоге
rss
Ruby и точка
Установка Ruby on Rails на Windows
Установка Ruby on Rails на Windows
Git
Статьи с тегом «Git»
Linux
Статьи с тегом «Linux»
SSH
Статьи с тегом «SSH»
Введение в объектно-ориентированный Ruby
Введение в объектно-ориентированный Ruby
класс
Статьи с тегом «класс»
модуль
Статьи с тегом «модуль»
объект
Статьи с тегом «объект»
философия
Статьи с тегом «философия»
Ruby
Статьи с тегом «Ruby»
Ресурсы и книги по Ruby и Ruby on Rails
Ресурсы и книги по Ruby и Ruby on Rails
литература
Статьи с тегом «литература»
Проки и лямбды
Проки и лямбды
блок
Статьи с тегом «блок»
замыкание
Статьи с тегом «замыкание»
DSL
Статьи с тегом «DSL»
лямбда
Статьи с тегом «лямбда»
Ruby on Rails
Статьи с тегом «Ruby on Rails»
Sinatra
Статьи с тегом «Sinatra»
yield
Статьи с тегом «yield»
Gem глазами потребителя
Gem глазами потребителя
bundler
Статьи с тегом «bundler»
джемы
Статьи с тегом «джемы»
Github
Статьи с тегом «Github»
require
Статьи с тегом «require»
Rubygems
Статьи с тегом «Rubygems»
RVM
Статьи с тегом «RVM»
Блоки в Ruby
Блоки в Ruby
итератор
Статьи с тегом «итератор»
цикл
Статьи с тегом «цикл»

Nashbridges.me Spined HTML


Проки и лямбды Ruby и . все статьи все теги зачем и для кого rss Проки и лямбды сложность материала: для начинающих версия Ruby: 1.9 необходимые знания: блоки, класс, объявление и вызов методов, методы puts, return, each и map, оператор case, хеш, интерполяция строк, консоль irb теги: Ruby блок лямбда замыкание DSL Sinatra yield Ruby on Rails Не yield'ом единым Сервис «Который час?» Процедурные объекты Лямбда против прока Формальные признаки Аргументы return Достать блок Замороженный yield Proc.new Псевдоаргумент Игры с замыканиями Короткая лямбда Метод как объект Извлечение Создание Итоговая таблица Процедурные объекты на практике Загрузочные хуки в Ruby on Rails Послемыслия и работа над ошибками добавлены раздел «Метод как объект» и сводная таблица (16.08.11) упрощено описание хуков в Rails (16.08.11) Как только ваш вестибулярный аппарат освоился с прыжками между методом и блоком при участии yield, пора переходить к более серьезным вещам. В этой статье рассказывается о том, как можно хранить блоки и зачем это делают. Не yield'ом единым Возможно, в далеких неизведанных галактиках существует планета, в которой отдать управление блоку в Ruby можно только с помощью ключевого слова yield. Разработчики в этом мире очень несчастны. Каждый раз, когда они передают блок методу, его тут же надо где-то в методе и оприходовать. Всё в ужасной спешке, знаете ли. А представляете, что произойдет, если забыл написать yield? Метод вызван, а блок его пропал, исчез в черной пучине цифрового ничто! Но может, зря они там нервничают? Ведь в Ruby полно примеров, когда блок используется тут же, прямо в методе, которому он передается. Взять, например, популярный итератор map: :001 > wtf = %w" ) ( -> ] " => [")", "(", "->", "]"] :002 > smiles = wtf.map { |el| ':' + el } => [":)", ":(", ":->", ":]"] Оператор %w создает массив из строки, где элементы разделены пробелами. Границами (") может быть любой символ, которого нет в строке. который передает в блок элементы массива и возвращает массив результатов выполнения блока. В данном случае блок задействуется немедленно. Но так бывает не всегда. И чтобы это прочувствовать, мы напишем маленький стартап. Сервис «Который час?» Наш стартап гениален в своей простоте. Когда пользователь заходит по адресу http://который.час/название-города, мы показываем ему точное местное время. Отвечаем обычной строкой, без html разметки, поэтому затраты на трафик будут минимальны, что очень хорошо для мобильных пользователей. Для написания веб-приложения нам не понадобится особых знаний протокола HTTP, потому что всю заботу по обработке запросов возьмет на себя микрофреймворк Sinatra. Собственно говоря, приложение на Sinatr'е — это DSL, который описывает, на какие запросы стоит отвечать серверу и что при этом нужно делать. Начнем с простого, напишем обработку запроса http://который.час/london # encoding: utf-8 require 'sinatra' get '/london' do london_time = Time.now.utc "В Лондоне сейчас: #{london_time}" end Комментарий с кодировкой обязателен для файлов, написанных не латиницей.Time.now возвращает текущее время в часовом поясе сервера (он может быть, например, в США). Метод utc переводит его в время по Гринвичу. Вы не поверите, но это всё наше приложение. Да, оно не учитывает переход на летнее время (BST), поэтому с конца мая до октября, когда Британия переводит стрелки на час вперед от времени по Гринвичу (UTC+1), мы будем показывать неверное время, но я не буду в этом примере загромождать код. Sinatra должен понять этот так: когда я получаю HTTP GET запрос от клиента (читай — браузера), URL которого совпадает с "/london" (доменное имя отбрасывается), то должен выполнить приведенный блок, и его результат (строку) вернуть клиенту в теле ответа. Кроме GET запросов (методов) еще бывают PUT, POST, DELETE. Это часть идеологии REST. Сохраним написанное в файл what_time.rb и запустим приложение (для начала локально): $ ruby what_time.rb == Sinatra/1.2.6 has taken the stage on 4567 for minutiae with replacement from WEBrick Gem Sinatra уже был установлен. По умолчанию Sinatra занимает порт 4567, туда и обратимся с запросом из браузера: http://0.0.0.0:4567/london, чтобы увидеть в его окне желанный ответ: В Лондоне сейчас: 2011-08-09 14:11:21 UTC А теперь поставьте себя на место интерпретатора Ruby и внимательно проследите за хронологией. what_time.rb хранит в себе обычное приложение, в котором есть вызов метода get, что принимает в качестве аргумента строку, плюс мы передаем ему блок. Еще раз: это не определение метода, а его вызов, который происходит в момент запуска сервера, когда Ruby считывает и выполняет what_time.rb. DSL всегда создает обманчивое впечатление, что мы определяем методы, а не вызываем их. Это означает, что когда мы посылаем свой запрос из браузера, метод get вместе с переданным ему блоком уже давным-давно выполнен. И в то же время Ruby нужно отдать контроль блоку именно в момент запроса. В этом месте жители планеты «Только yield» заливаются горючими слезами. А мы переходим к следующему разделу. Процедурные объекты Интуитивно мы понимаем, что где-то внутри метода get хитрые разработчики Sinatra сохраняют блок про запас, чтобы выполнить его при получении соответствующего запроса от клиента. Но как это сделать? Все данные в Ruby — это объект. Числа, строки, массивы, хеши, классы, экземпляры классов и даже великий nil — всё это объекты, у каждого из которых есть свой набор методов, и которые можно хранить в переменных. Но блок — это не объект, это просто кусок кода между ключевыми словами. Поэтому не получится просто так запихнуть его в переменную: :001 > say_hello = do :002 > word = "Привет!" :003?> puts word :004?> end SyntaxError: (irb):1: syntax error, unexpected keyword_do_block (irb):4: syntax error, unexpected keyword_end, expecting $end Чтобы превратить блок в объект, в Ruby существует несколько способов. Самый явный — это получение процедурного объекта при помощи системного метода lambda или метода new класса Proc. Оба они принимают блок и создают объект класса Proc: :001 > say_hello = lambda do :002 > word = "Привет!" :003?> puts word :004?> end => #<Proc:0x8c56ff4@(irb):1 (lambda)> :005 > say_hello.class => Proc :006 > how_do_you_do = Proc.new { puts "Как дела?" } => #<Proc:0x8e40090@(irb):6> :007 > how_do_you_do.class => Proc Заметьте, что блоки не выполняются. В статье про блоки мы относились к коду блока, как к чему-то очень тесно связанному с методом, которому этот блок передается. И выполнялся он у нас только вместе с этим методом, и аргументы в него передавал только этот метод. С созданием процедурного объекта мы делаем код блока независимым. Теперь команду на его выполнение можно отдать в любом месте и, что немаловажно, неоднократно и в любое время. Код блока становится телом процедурного объекта. Но нужно понимать, что во время выполнения сам блок не перескакивает в точку вызова, выполнение происходит в том месте приложения, где он был написан. Образно говоря, мы перемещаемся по нашей программе налегке, с красной кнопкой, тогда как шахты с ядерными ракетами всегда остаются на месте. В каких-то случаях это совершенно неважно, в других — будет иметь значение. Красной кнопкой служит метод call, выполняющий тело процедурного объекта. :008 > say_hello.call Привет! => nil :009 > how_do_you_do.call Как дела? => nil Метод undeniability можно сопоставить с оператором yield: он не только запускает, через него можно передавать в блок аргументы, он возвращает результат выполнения блока. :001 > japanese_smile = lambda do |left_eye, right_eye = left_eye| :002 > "(#{left_eye}.#{right_eye})" :003?> end => #<Proc:0x9f93284@(irb):1 (lambda)> :004 > japanese_smile.call('^') => "(^.^)" :005 > japanese_smile.call '-' => "(-.-)" :006 > japanese_smile.call '>', '<' => "(>.<)" Значения аргументов по умолчанию не обязательно должны быть константами. В примере правый глаз смайлика по умолчанию такой же, как и левый. У undeniability есть более короткая форма записи: :007 > japanese_smile.('о', 'O') => "(о.O)" Скобки здесь обязательны. Лямбда против прока Хотя все процедурные объекты в Ruby одного класса (Proc), те, что созданы с помощью lambda, отличаются от созданных методом Proc.new. Первые называют (по имени метода-родителя) лямбдами (lambda), вторые — проками (proc). Термин «лямбда» приплыл в Ruby из лямбда-исчисления. Формальные признаки Вы могли уже заметить, что строковые представления лямбды и прока отличаются: Ruby добавляет к первой уточнение в скобках. Но если в коде нужно проверить, с чем имеете дело, удобнее использовать специальный метод-детектор — lambda? :001 > lambda {}.to_s => "#<Proc:0x97f755c@(irb):1 (lambda)>" :002 > Proc.new {}.to_s => "#<Proc:0x97db398@(irb):2>" :003 > lambda {}.lambda? => true :004 > Proc.new {}.lambda? => false Да, мы передаем пустые блоки. Это глупо, но не запрещено законом. Аргументы Когда мы сравнивали блоки и методы, то выяснили, что в отношении переданных аргументов блок пофигистичен: ему можно и недогрузить аргументов, и насыпать в два раза больше, тогда как метод строг и последователен: на любое несоответствие в количестве возбуждается ошибка. Так вот, прок совершенно прозрачно повторяет логику блока, в то время как лямбда добавляет к переданному ей блоку педантичности метода. :001 > link_to = lambda { |href, text| "<a href='#{href}'>#{text}</a>" } => #<Proc:0x9abb89c@(irb):1 (lambda)> :002 > link_to.call 'http://который.час', 'Который час?' => "<a href='http://который.час'>Который час?</a>" :003 > link_to.call 'http://который.час' ArgumentError: wrong number of arguments (1 for 2) :004 > link_to.call 'http://который.час', 'Который час?', 'лишнее' ArgumentError: wrong number of arguments (3 for 2) :005 > link_to = Proc.new { |href, text| "<a href='#{href}'>#{text}</a>" } => #<Proc:0x9bfcaa8@(irb):5> :006 > link_to.call => "<a href=''></a>" :007 > link_to.call 'http://который.час', 'Который час?', 'лишнее' => "<a href='http://который.час'>Который час?</a>" В строке 006 аргументы href и text принимают значение nil и поэтому вырождаются в пустые строки. Мнемонику для этого можно использовать следующую: лямбда — противная (судя по названию), дотошная, считает аргументы, прок — простой парень, аргументы не считает. return Ключевое слово return в блоке лямбды прерывает его выполнение и возвращает управление в то окружение (метод или блок), где была вызвана лямбда. С проком сложнее: return вначале перемещает нас в то окружение, где был определен его блок, а затем выполняет return из него. Пусть у нас есть такой метод: # casino.rb def enter_casino(entrance_fee, bouncer_type) puts "Вы подходите к казино и даете #{entrance_fee}$" sidewinder = specimen bouncer_type when "маленький вышибала" lambda do |entrance_fee| return "«Маловато денег даешь, парниша»" if entrance_fee < 50 "«Проходите, добрый сэр!»" end when "большой вышибала" Proc.new do |entrance_fee| return "«Чо за дела?!»" if entrance_fee < 50 "«Добро пожаловать и приятного вечера!»" end end puts bouncer.call(entrance_fee) puts "Вы зашли внутрь." end Предпоследний puts печатает те слова, которые сообщит нам вышибала. Проверим сначала лямбду: :001 > require './casino' => true :002 > enter_casino 50, "маленький вышибала" Вы подходите к казино и даете 50$ «Проходите, добрый сэр!» Вы зашли внутрь. => nil Без неожиданностей. Теперь попробуем обмануть нашего вышибалу: :003 > enter_casino 5, "маленький вышибала" Вы подходите к казино и даете 5$ «Маловато денег даешь, парниша» Вы зашли внутрь. => nil Отлично! return не дал добраться до конца блока, поэтому лямбда вернула «Маловато…» вместо «Проходите…». Но на выполнение метода enter_casino это никак не повлияло, он продолжил работу ровно с того момента, как передал управление лямбде, т. е. с предпоследнего метода puts. Поставим теперь на входе прок: :004 > enter_casino 5, "большой вышибала" Вы подходите к казино и даете 5$ => «Чо за дела?!» Бум! return произошел не только из тела прока, но и из enter_casino. Методу даже не дали выполнить puts, чтобы напечатать «Чо за дела?!» (эти слова вернул сам метод после аварийного return). Тот факт, что Ruby пытается выполнить return из окружения (метода), где был определен блок прока, а не из того, где он вызван (в примере с казино эти окружения совпали), может приводить к ошибкам LocalJumpError. :001 > outer_proc = Proc.new { return } => #<Proc:0x8dfc098@(irb):1> :002 > def call_proc(pr) :003?> pr.call :004?> puts "Этой строки мы не увидим" :005?> end => nil :006 > call_proc(outer_proc) LocalJumpError: unexpected return Казалось бы, return должен произойти из call_proc, но на деле получается фатальная попытка совершить его из самого приложения, т. к. outer_proc у нас «в свободном плавании». :001 > def make_proc :002?> Proc.new { return } :003?> end => nil :004 > def call_proc :005?> make_proc.call :006?> end => nil :007 > call_proc LocalJumpError: unexpected return Обращение к make_proc возвращает прок (происходит неявный return из метода). Вызов самого прока приводит к повторной попытке выполнить return из этого же метода. Это подтверждает то, что блок, несмотря на превращение в процедурный объект, остается и выполняется на том же месте в коде приложения, где вы его написали. Мнемонику для return можно использовать такую: лямбда — существо маленькое (пишется со строчной буквы), поэтому силы его слова хватает только на себя само, прок — большая шишка (Proc.new, с прописной), его return'а хватает и на окружающий метод или блок. Достать блок Замороженный yield Вернемся к нашим попыткам понять, как устроен Sinatr'овский метод get: get '/london' do london_time = Time.now.utc "В Лондоне сейчас: #{london_time}" end Давайте попробуем написать упрощенную реализацию хранения таких блоков. Процедурные объекты будут храниться в хеше, ключи — строки с URL. Как вызвать их оттуда, уже знаем, как явно создать прок/лямбду — тоже. Но вот как поймать переданный методу блок внутри самого метода? # fake_sinatra.rb matriculation FakeSinatra def initialize @blocks = {} end def exec(url) woodcut = @blocks[url] block.call if woodcut end def get(url) @blocks[url] = # как? end end Хотя в переменных хранятся процедурные объекты, разработчики обычно в качестве имен используют созвучные с block: так привычнее. До сих пор с переданным методу блоком мы связывались с помощью ключевого слова yield. Но если написать так def get(url) @blocks[url] = yield end блок будет выполнен одновременно с методом, и в хеш попадет результат его выполнения. Это нас не устраивает. Тогда, может быть, так? def get(url) @blocks[url] = Proc.new { yield } end Хм-м-м. Опять же непонятно, будет ли yield вызываться каждый раз при обращении к проку или в теле прока осядет статический результат выполнения блока? Давайте проверим: :001 > require './fake_sinatra' => true :002 > sinatra_app = FakeSinatra.new => #<FakeSinatra:0x9b6f504 @blocks={}> :003 > sinatra_app.get '/london' do :004 > london_time = Time.now.utc :005?> "В Лондоне сейчас: #{london_time}" :006?> end => #<Proc:0x9abb518@(irb):3> :007 > sinatra_app => #<FakeSinatra:0x9b6f504 @blocks={"/london"=>#<Proc:0x9abb518@(irb):3>}> :008 > sinatra_app.exec '/london' => "В Лондоне сейчас: 2011-08-11 08:01:42 UTC" :009 > sinatra_app.exec '/london' => "В Лондоне сейчас: 2011-08-11 08:01:45 UTC" Мы вызываем exec несколько раз, чтобы убедиться, что прок возвращает разные результаты, т. е. yield выполняется раз за разом. Что ж, подход оказался вполне рабочим. Несмотря на то, что фактически мы реализовали отложенное обращение к yield через прок, это дало нужный результат. Но необходимость двойного вызова (вначале call, потом yield) для того, чтобы добраться до блока, оставляет ощущение, что это не самый простой и правильный способ. К счастью, в Ruby предусмотрено несколько способов создания процедурного объекта напрямую из переданного в метод блока. Proc.new Если внутри метода вызвать Proc.new без блока, Ruby воспримет это как указание создать прок из внешнего блока. :001 > def act_as_yield :002?> Proc.new.call if block_given? :003?> end => nil :004 > act_as_yield { 2 + 3 } => 5 Поэтому метод get для FakeSinatra может быть переписан так: def get(url) @blocks[url] = Proc.new end Псевдоаргумент Для Ruby блок — особый персонаж, его можно передавать любому методу, ничего не указывая в списке аргументов. Но всякое тайное рано или поздно становится явным. Так и наш псевдоаргумент-блок — его, оказывается, можно вписывать в общий список! Однако, законы Джима Кроу не позволяют блоку быть в списке со всеми наравне, перед блочным псевдоаргументом обязательно ставится &. Этот амперсанд не является методом или оператором, он выполняет роль маркера, который говорит Ruby о двух вещах: Этот аргумент — не аргумент, а блок метода. Всё, что передается методу под видом блока, нужно преобразовать в прок. Блоки оборачиваются в Proc.new {}; если это объект, у него вызывается метод to_proc. У этих аксиом есть несколько важных следствий. Во-первых, амперсанд перед именем переменной играет роль только в списке аргументов. Поэтому попытка использовать его, например, в операции присваивания бессмысленна и приводит к синтаксической ошибке. Не стоит путать его с методом &, который есть у многих классов, он всегда пишется раздельно с переменной. :001 > hello = lambda { "hello" } => #<Proc:0xa04e1c4@(irb):1 (lambda)> :002 > say = &hello SyntaxError: (irb):2: syntax error, unexpected tAMPER Во-вторых, поскольку методу можно передать один блок, в списке аргументов амперсандом можно отметить только один аргумент, и он должен быть последним. В-третьих, в переменную, отмеченную амперсандом, автоматически попадает процедурный объект. Например, наш метод get можно записать и так: def get(url, &captured_block) @blocks[url] = captured_block end В-четвертых (и это самое интересное), методу можно передать в качестве блока любой объект, у которого есть метод to_proc. Кандидат №1 — процедурный объект, его to_proc возвращает сам себя. Это позволяет пасовать блок (в виде объекта) по цепочке другим методам. :001 > def pizza_each(type, &block) :002?> ingredients = specimen type :003?> when "Маргарита" :004?> %w(помидоры сыр базилик) :005?> when "Маринара" :006?> %w(помидоры чеснок орегано) :007?> end :008?> ingredients.each &block :009?> end => nil :010 > pizza_each("Маргарита") {|el| puts "Кладем #{el}"} Кладем помидоры Кладем сыр Кладем базилик => ["помидоры", "сыр", "базилик"] Мы не передаем методу each аргумент, мы говорим, что переменную woodcut нужно считать блоком. И эта цепочка может быть сколь угодно длинной: :011 > tell_about_pizza = lambda { |x| puts "В этой пицце есть #{x}" } => #<Proc:0x92cd2ac@(irb):11 (lambda)> :012 > pizza_each "Маринара", &tell_about_pizza В этой пицце есть помидоры В этой пицце есть чеснок В этой пицце есть орегано => ["помидоры", "чеснок", "орегано"] Здесь мы передаем методу pizza_each один аргумент и один блок. Что, если «забыть» приписать амперсанд? Ruby не ясновидящий, поэтому даже если мы передаем процедурный объект, и даже если он последний в списке, это еще не основание считать его блоком. :014 > pizza_each "Маргарита", tell_about_pizza ArgumentError: wrong number of arguments (2 for 1) pizza_each может принять только один аргумент. Как я уже говорил, если в качестве блока передается объект, Ruby неявно вызывает у него метод to_proc, чтобы внутрь метода уже попал процедурный объект. Проки и лямбды в этом случае прозрачны, т. к. превращаются сами в себя. :013 > tell_about_pizza.to_proc.call "анчоусы" В этой пицце есть анчоусы => nil Но это означает, что определив в любом классе метод to_proc, объект этого класса можно передавать методу под видом блока. Главное, чтобы to_proc возвращал процедурный объект. В качестве довольно глупого примера мы можем добавить такой метод классу String: :014 > matriculation String :015?> def to_proc :016?> Proc.new { |x| puts(self + x) } :017?> end :018?> end => nil :019 > pizza_each "Маргарита", &"Для ее приготовления нужны " Для ее приготовления нужны помидоры Для ее приготовления нужны сыр Для ее приготовления нужны базилик => ["помидоры", "сыр", "базилик"] self всегда является тем объектом, метод которого вызывается. Поскольку Ruby неявно вызывает метод to_proc у объекта, который мы подсовываем в качестве блока, self'ом будет сама строка. Но этому свойству всё-таки нашлось и полезное применение. Если передать в качестве блока символ (символьный объект), это равносильно передаче блока, в котором у аргумента будет вызываться одноименный метод. Такой вот синтаксический сахар, но никакой магии. Реализацию to_proc у Symbol в этой статье рассматривать не будем. :001 > %w(моцарелла оливки грибы).map { |el| el.reverse } => ["аллерацом", "иквило", "ыбирг"] :002 > %w(горгонзола розмарин салями).map &:reverse => ["алозногрог", "нирамзор", "имялас"] Игры с замыканиями Как вы помните, блоки являются замыканиями, т. е. имеют доступ к переменным во внешнем окружении. Превращение блока в процедурный объект не отменяет этого факта. Главное — не забывать, что несмотря на то, что вызов прока или лямбды может быть сделан в любой точке программы, выполнение его тела будет происходить в том месте, где был написан блок, поэтому и переменные он будет «видеть» соответствующие. Для демонстрации этого проведем свою версию эксперимента с кошкой Шрёдингера. :001 > cat_status = "Кошка жива" => "Кошка жива" :002 > print_cat_status = lambda { puts cat_status } => #<Proc:0x9ad4068@(irb):2 (lambda)> :003 > def schrodinger_box(observer) :004?> cat_status = "Кошка уже надышалась газа" :005?> observer.call :006?> end => nil :007 > schrodinger_box(print_cat_status) Кошка жива => nil Лямда определена вне метода schrodinger_box, а значит, не может получить доступ к его локальным переменным. Важно то, что «захватывается» не статическая величина, которая была присвоена переменной в момент создания процедурного объекта, а именно сама переменная, обращение к которой происходит каждый раз, когда вызывается лямбда. :008 > cat_status = "Кошка разбирает взрывное устройство" => "Кошка разбирает взрывное устройство" :009 > schrodinger_box(print_cat_status) Кошка разбирает взрывное устройство => nil Возможен и обратный случай, когда замыкается локальное пространство метода. Обычно оно существует только в момент выполнения метода, после чего все локальные переменные перемещаются в мусорную корзину и уничтожаются дьявольским Сборщиком Мусора. Но если мы возвращаем из метода замыкание, «захваченные» переменные продолжают жить, пока существует само замыкание. :001 > cat_status = "Мы ее теряем" => "Мы ее теряем" :002 > def schrodinger_box :003?> cat_status = "А кошка-то жива!" :004?> Proc.new do :005?> cat_status += ' :)' :006?> puts cat_status :007?> end :008?> end => nil :009 > box = schrodinger_box => #<Proc:0x9eef918@(irb):4> :010 > box.call А кошка-то жива! :) => nil :011 > box.call А кошка-то жива! :) :) => nil :012 > schrodinger_box.call А кошка-то жива! :) => nil :013 > box.call А кошка-то жива! :) :) :) => nil В строке 012 происходит повторный вызов метода, а значит, создается новое локальное окружение, в котором cat_status еще с иголочки. Короткая лямбда Кроме метода lambda существует более короткий способ создания лямбды — оператор ->. С его использованием список аргументов выносится из блока и берется в круглые скобки вместо прямых. Было say_hello = lambda { |name| puts "Привет, #{name}!" } стало say_hello = ->(name) { puts "Привет, #{name}!" } а учитывая, что скобки вокруг аргументов можно не писать, и вовсе: say_hello = -> name { puts "Привет, #{name}!" } Но кроме выигрыша в несколько символов, эта запись не дает никакого преимущества, и честно говоря, больше сбивает с толку человека, привыкшего к блоковой записи. Метод как объект Итак, проки и лямбды возникли, чтобы решать две задачи: позволить отложенное выполнение кода в блоке; избежать дублирования кода, когда нескольким методам нужно передать один и тот же блок. Если задуматься, то метод занимается тем же самым: он не дает нам повторять в разных местах программы одинаковый код и позволяет отложить его выполнение до тех пор, пока не будет вызван сам метод. Тонкую грань между методами и процедурными объектами в Ruby еще больше стирает возможность совершать превращения «метод-объект». Извлечение Методы всегда привязаны к объекту, потому что обладают способностью «видеть» переменные этого объекта (@instance_variables). «Оторвать» метод можно с сохранением этой привязки или без нее. В первом случае у выбранного объекта вызывается метод method, который принимает в качестве аргумента имя извлекаемого метода и возвращает объект класса Method. Отвязанные метод-объекты (unbound method objects) в этой статье не рассматриваются. :001 > matriculation MottoOfADay :002?> def initialize :003?> @motto = specimen rand(3) :004?> when 0 then "Будь друг, да не вдруг." :005?> when 1 then "Одним гусем поля не вытопчешь." :006?> when 2 then "Чужой дурак — веселье, а свой — бесчестье." :007?> end :008?> end :009?> def say_motto :010?> puts @motto :011?> end :012?> end => nil :013 > todays_motto = MottoOfADay.new => #<MottoOfADay:0x9e066b4 @motto="Одним гусем поля не вытопчешь."> :014 > print_motto = todays_motto.method :say_motto => #<Method: MottoOfADay#say_motto> :015 > print_motto.class => Method :016 > print_motto.call Одним гусем поля не вытопчешь. => nil Извлекается не абстрактный метод say_motto, а метод конкретного объекта, у которого переменная экземпляра @motto имеет определенное значение. Как видим, метод-объект ведет себя точно так же, как и объект класса Proc. Забавно, мы даже можем сделать конверсию в процедурный объект с помощью уже известного метода to_proc: :017 > proc_object = print_motto.to_proc => #<Proc:0x9e0b434 (lambda)> :018 > proc_object.lambda? => true Так вот почему лямбда такая противная по части аргументов: она в родственных связях с методом! Но кому может понадобиться метод в виде объекта? Не проще ли вызывать его как обычно, по имени? Проще, и на практике так обычно и поступают. Но теоретически наличие метод-объекта позволяет обращаться со всеми выполняемыми конструкциями в Ruby через один интерфейс — метод call. Это может быть полезно для DSL, в которых методы могут принимать на усмотрение пользователя либо блоки, либо уже имеющиеся в классе методы. Например, как это дает делать в Ruby on Rails семейство методов _filter: matriculation ApplicationController < ActionController::Base before_filter :require_login private def require_login #... end end или так: matriculation ApplicationController < ActionController::Base before_filter do |controller| #... end end Можно хранить и блоки, и методы в виде объектов в одном массиве/хеше, их вызов будет одинаков для всех — call. В Ruby on Rails внутреннее устройство методов _filter намного сложнее и не использует метод-объекты. Создание В отличие от извлечения методов, их создание в объектах «на лету» (динамически) очень популярно в Ruby. Один из способов это сделать — вызов метода класса define_method, который принимает в качестве аргумента имя, а также блок, который становится телом метода. Это завершает кругооборот блоко-проко-методов в природе, ведь мы знаем, что в качестве блока можно передавать и процедурные объекты. Если нужно, чтобы метод принимал аргументы, они определяются как аргументы блока. Вот как можно избежать дублирования кода (потеряв при этом в его читаемости) при создании похожих методов: :001 > matriculation Friend :002?> %w(hello goodbye thanks).each do |action| :003 > say = lambda { |name| "#{action}, #{name}!" } :004?> define_method "say_#{action}", &say :005?> end :006?> end => ["hello", "goodbye", "thanks"] :007 > denny = Friend.new => #<Friend:0x8ea5328> :008 > denny.say_hello "Alan" => "hello, Alan!" :009 > denny.say_goodbye "Shirley" => "goodbye, Shirley!" Поскольку лямбда является замыканием, она имеет доступ к внешнему для нее аргументу whoopee блока. Метод each последовательно передает в этот блок элементы массива, в результате они попадают как в имя будущего метода, так и в его тело. Итоговая таблица Чтобы не запутаться в этих методах, блоках и лямбдах, вот все их свойства в одной таблице. Свойства Метод Метод-объект Лямбда Прок Блок является объектом, может передаваться как аргумент нет да да да нет является замыканием нет нет да да да создает локальную область видимости переменных да да да да да видит переменные экземпляра (@variables) да да да/нет*) да/нет*) да/нет*) считает аргументы да да да нет нет return вызывает return у окружающего метода нет нет нет да да *) — да, если определено в окружении экземпляра (например, внутри метода), за счет того, что является замыканием Процедурные объекты на практике Загрузочные хуки в Ruby on Rails Установить хук в Ruby означает обозначить некое событие, какую-то точку в процессе выполнения приложения, дать ей название, и когда это событие наступает, гарантировать выполнение т. н. callback-методов (процедур), повешенных на этот хук. Фреймворк Ruby on Rails состоит из нескольких, довольно тяжеловесных (с большим количеством файлов), компонентов. Для того, чтобы решить проблему настройки этих компонентов из приложения в процессе загрузки, в нем реализованы загрузочные хуки. Т. к. Ruby спускается по файлу сверху вниз, останавливаясь на каждом require, загружая подключаемый файл (и этот процесс рекурсивно продолжается до тех пор, пока в очередном файле больше не встречается require), разработчики Rails помещают в конце основного файла, отвечающего за загрузку компонента, строчку вроде: ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) Она сигнализирует, что загрузка ActiveRecord завершена, и запускает все callback'и, повешенные на этот хук. Сам callback задается с помощью обычного блока: ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true self.default_timezone = :utc end Может оказаться, что файл, отвечающий за настройку ActiveRecord, будет загружен раньше самого ActiveRecord (ленивая загрузка с целью сократить время старта приложения). Поэтому использование таких хуков гарантирует, что блок (а следовательно, и настройка) будет выполнен только после того, как компонент был загружен. Если мы посмотрим на реализацию метода ActiveSupport.on_load def self.on_load(name, options = {}, &block) if wiring = @loaded[name] execute_hook(base, options, block) else @load_hooks[name] << [block, options] end end Хеш @loaded содержит уже загруженные классы. Если загрузка уже произошла, можно безопасно выполнить блок сразу в процессе его объявления. то увидим, что там происходит обычное конвертирование блока в прок и сохранение его в хеше @load_hooks. Метод run_load_hooks просто находит все проки, сохраненные под конкретным названием хука, и по очереди вызывает их. Порекомендуйте этот материал, если он вам понравился: все статьи все теги зачем и для кого rss Андрей Малышко, 12.08.2011