Курсы. День 10

By leoniddinershtein, November 23rd, 2010
blogger

Конспект Евгения

Блоки
Блоки, процедуры и лямбды — замыкания (closures) — являются важным аспектом языка Ruby, с одной стороны, и малопонятным с другой. Малопонятным в основном из-за того, что Ruby подходит к замыканиям в значительной степени уникальным образом, к тому же в Ruby существуют разные способы использования замыканий.

Способ первый. Proc / Lambda
Любой кусок кода в руби можно выделить в особой тип объекта - блок. Блок кода явялется объектом класса Proc. для его создания необходимо выполнить

some_block = Proc.new { puts 'from some blcok' }
или
some_block = lambda do
puts 'from lambda'
end

Сохраненный таким образом код можно использовать по разному. можно передвать в функции в качестве параметров, можно просто вызвать. Для передачи параметров в блок нужно определить его принимающим параметры. Список переменных в |аргумент1| будет определять параметры блока.
some_block_with_parameter = lambda do |name|
puts 'from lambda'
end

для исполнения кода нужно вызвать метод .call объекта Proc, в зависимости от блока - с параметрами или без.
some_block.call
some_block_with_parameter.call :argument

а вот пример работы функции с блоков
def my_func(block)
puts 'before block called'
block.call
puts 'after block called'
end
some_block = Proc.new { puts 'from some blcok' }
my_func(block)
=>
before block called
from some blcok
after block called

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

Способ второй. блоки
В отличие от от lambda/proc, блоки кода можно использовать только один раз, но и процедура их записи на порядок короче.
def my_func(&block)
puts 'before block called'
block.call
puts 'after block called'
end

# другой способ передачи блока
my_func do
puts 'code from block'
end

или с параметром

def my_func(&block)
puts 'before block called'
block.call 'some name'
puts 'after block called'
end

my_func do |name|
puts 'code from block ' + name
end

# можно и Proc передать, при необходимости

block = Proc.new { puts 'proc' }
my_func(&block)

В сокращенном виде это можно писать и так:

def my_func
# это проверка пришел ли блок
return unless block_given?
puts 'before block called'
yield 'some name'
puts 'after block called'
end

my_func { |name| puts 'called in function ' + name }

В завершении приведу примерную реализацию итератора по массиву
class Array
def my_each
i = 0;
while i < self.size
yield self[i]
i+=1
end
end
end
(1..10).to_a.my_each { |element| puts element }

Отметим, что замыкания широко используются в ruby, более того, ни одна программа не обходится без них.

Вопросы на дом
- найти отличия между lambda и proc
- разница между передачей блока как block и как &block

Модули. Классы
Наиболее очевидное применение модулей - в качестве неймспейсов.
module Mathic
def self.abs
puts 'calculate abs'
end
end

module Party
def self.abs
puts 'some drinking to absolute drinking state'
end
end
def abs
puts 'another method to_s'
end
abs
Mathic.abs
Party::abs

использование различных модулей позволяет разделить эти методы и переписать случайно какой либо из них.

Второе, и более частое использование модулей - в качестве примесей ( mixins ). Mixins - это механизм, с помощью которого в руби реализуется добавление общих методов классы. Механизм более удобный и простой по сравнению с наследованием, особенно множественным или многоуровневым.

module Movable
def move
puts self.name + ' moved'
end
end

class Tank
include Movable

def name
'tank'
end
end

class Ship
include Movable

def name
'ship'
end
end
Ship.new.move
Tank.new.move

Как видно, добавленеи модуля через include расширяет функциональность объекта класса. для расширения функциональности самого класса необходимо воспользоваться ключевым словом extend. Продемонстрирую разницу между extend/example:

module Foo
def foo
puts 'foo'
puts self.inspect
end
end

module Bar
def bar
puts 'bar'
puts self.inspect
end
end

class Unit
extend Foo
include Bar
end

Unit.foo
Unit.new.bar

А теперь более реальный пример. Расширение функциональности происходит в нужный момент в нужном классе.

module Movable
# метод вызывается момент include Movable
def self.included(base)
# расширяем функциональность класса методом acts_as_movable_object
base.send :extend, ClassMethods
end

module ClassMethods
def acts_as_movable_object
send :include, InstanceMethods
end
end

module InstanceMethods
attr_accessor :name

def move
puts self.name + ' moved'
end

def stop
puts self.name + ' stopped'
end
end
end

class Unit
include Movable
end

class Tank < Unit
# метод определен
acts_as_movable_object
end

a = Unit.new
a.name = 'unit'
a.move
a.stop

легко продолжить данный способ до любого количества модулей в меру своей фантазии.

class Unit
include Movable
include Shootable
include Flyable
include Swimable
end

class Ship < Unit
acts_as_shootable
acts_as_swimable
end

class Plane < Unit
acts_as_flyable
end

5 Comments | Posted in RubyOnRails | RSS feed for comments on this post
erthad | November 25, 2010 @ 1:20 pm

Такой код не совсем ruby-way:
while i < self.size
yield self[i]
i+=1
end

Я бы написал что-то типа (0..self.size).each { |i| yield self[i] }
Если мы переопределяем Array#each, это не значит, что мы не сможем использовать Range#each — он в них наследуется из Enumerable.

erthad | November 25, 2010 @ 1:52 pm

Вопрос к флэтсофту:
А можете на сайте сделать RSS еще и для комментариев?

Артем | December 2, 2010 @ 12:26 am

меня на курсах сегодня просили кинуть ссылку на условие одного из вариантов заданий:
https://gist.github.com/731cb7b64a1b9f569d35

Oleg Kurnosov | December 2, 2010 @ 11:58 am

тема :)

выпускной когда уже?) кто с красным дипломом курсы окончил понятно уже у вас там?)

Артем | December 17, 2010 @ 3:41 pm

вот ссылка на мое тестовое задание:
https://github.com/Biserov/News
на heroku.com выложу позднее
так же пока отсутствуют тесты

Leave a comment

* - required information