2019
Enumerator
реализует «внешние итераторы»1, 3, "cat"]
a = [dog: "canine", fox: "vulpine"}
h = {# Создаём нумераторы
enum_a = a.to_enum
enum_h = h.to_enum# => 1
enum_a.next # => [:dog, "canine"]
enum_h.next # => 3
enum_a.next # => [:fox, "vulpine"] enum_h.next
to_enum
на коллекции или enum_for
с указанием названия итератора1, 3, "cat"]
a = [# Созадаём нумератор
enum_a = a.each # => 1
enum_a.next # Создаём нумератор
enum_b = a.to_enum :each) # Создаём нумератор enum_c = a.enum_for(
Нумераторы являются объектами класса Enumerator
, который включает в себя модуль Enumerable
, что делает доступным для нумераторов всех «классных» методов
loop
Задачей данного метода является бесконечный вызов блока. Если внутри блока используются нумераторы, то выход будет осуществлён, когда закончатся значения в нумераторе
1, 2, 3].to_enum
short = ['a'..'z').to_enum
long = (do
loop " #{short.next} - #{long.next}"
puts end
Метод each_with_index
определён в модуле Enumerable
result = []'a', 'b', 'c'].each_with_index do |item, index|
[
result << [item, index]end
# => [["a", 0], ["b", 1], ["c", 2]] result
Метод with_index
определён в классе Enumerator
result = []"dog".each_char.with_index do |item, index|
result << [item, index]end
# => [["d", 1], ["o", 2], ["g", 2]] result
enum_for
Методу enum_for
можно передать название метода-итератора, который будет предоставлять значения последовательности
"cat".enum_for(:each_char)
enum = # => ["c", "a", "t"] enum.to_a
Если итератор ожидает аргументов, то их следует передать после имени метода
1..7).enum_for(:each_slice, 3)
enum_in_threes = (# => [[1, 2, 3], [4, 5, 6],
enum_in_threes.to_a # [7]]
Нумераторы могут быть созданы на основе обычного блока, который предоставляет очередные значения
Enumerator.new do |yielder|
numbers = 0
number = 1
count = do
loop
number += count1
count +=
yielder.yield numberend
end
5.times { print numbers.next, " " }
5) # Доступны методы Enumerable puts numbers.first(
Если генерирующий блок способен предоставлять бесконечное число значений, то его надо указать “ленивым”
Enumerator.new do |yielder|
numbers = 0
number = do
loop 1
number +=
yielder.yield numberend
end.lazy
10)
puts numbers.all.first(
puts numbers.select { |val|10 == 0 }.first(5)
val %
puts numbers.select { |val|3).zero? }.first(10) (val %
Хорошей практикой при создании собственного итератора является возвращение нумератора в случае, когда блок не ассоциирован с данным методом
def iterator
return enum_for(:iterator) unless block_given?
...end
В результате ваш собственный итератор можно будет использовать как встроенные итераторы: либо в форме ассоциации с болоком, либо в форме получения нумератора
Some.new
some =
some.iterator { ... }
numer = some.iterator numer.each { ... }
Зачастую необходимо выполнять связные действия, например открытый файл обязательно должне быть закрыт
class File
def self.open_and_process(*args)
File.open(*args)
f = yield f
f.close()end
end
File.open_and_process("testfile", "r") do |file|
while line = file.gets
puts lineend
end
self
, относятся к классу, а не к объекту класса (“статические”)*args
в аргументах метода open_and_process
собирает все аргументы в массив args
*args
в вызове метода open
раскрывает содержимое массива и записывает их как аргументы метода**opts
для обработки именованных аргументовFile.open
block_given?
проверяет наличие блока и позволяет реализовать альтернативное поведениеif block_given?
yield file
result = file.close
Данная техника применяется в итераторах Array, Hash, Enumerable и т.д. для обработки ситуации работы метода с блоком и без него. Если вы не ассоциировали блок с итератором, то он вернёт нумератор
Блоки похожи на анонимные методы, однако с ними можно общаться как с объектами: сохранять в переменные…
class ProcExample
def pass_in_block(&action)
@stored_proc = action
end
def use_proc(parameter)
@stored_proc.call(parameter)
end
end
ProcExample.new
eg = do |param|
eg.pass_in_block "The parameter is #{param}"
puts end
99) eg.use_proc(
Блоки представлены классом Proc
Proc.new do |param|
reach = "You called #{param}"
puts end
42) # => You called 42
reach.call('scar') => You called scar reach.call(
Объекты можно вернуть из методов
def create_block_object(&block)
blockend
do |param|
reach = create_block_object "You called #{param}"
puts end
Блоки можно создавать с помощью метода lambda
do |param1, param2|
bo = lambda "You called me with #{param1}"
puts end
42, 'reason') bo.call(
Или использовать краткий синтаксис ->
do
bo = -> (param1, param2) "You called me with #{param1}"
puts end
'dog', 'barks') bo.call(
nil
вывод Используйте лямбды, т.к. они предоставляют простую модель использования и гарантии
return
выходит из лямбды, а не из метода, её вызвавшую.return
приведёт к выбросу исключения, если будет вызвано вне связанного методавывод Не используйте ключевое слово return
внутри блоков
Лямбды и блоки определены в едином классе Proc
Для вызова блока он предоставляет следующие методы:
action = -> (param) { ... }
#call(params)
: action.call(42)
#.(params)
: action.(42)
#[params]
: action.[42]
#yield(params)
: action.yield(42)
Все формы равносильны между собой. Рекомендуется использовать #call()
Замыкание - возможность доступа к переменным, объявлённых вне блока
def n_times(thing)
lambda {|n| thing * n }end
23)
p1 = n_times(3) # => 69
p1.call(2) # => 46 p1.call(
thing
метода n_times
находится в области видимости выражений блокаthing
Можно реализовать простой генератор следующим образом
def power_proc_generator
1
value =
lambda { value += value }end
power_proc = power_proc_generator# => 2
puts power_proc.call # => 4 puts power_proc.call
Современным способом описания коротких лямбд является
-> params { ... }"In proc1 with #{arg}"} proc1 = -> arg {puts
lambda
длиннее ->
def my_while(cond, &body)
while cond.call
body.callend
end
0
a = 3 } do
my_while -> { a <
puts a1
a += end
Любой класс, реализующий метод #to_proc
может быть преобразован в блок с помощью оператора &
class Greater
def initialize(greating)
@greating = greating
end
def to_proc
"#{@greating}, #{name}!" }
proc {|name| end
end
Greater.new("Hi")
hi = Greater.new("Hey")
hey = "Bob", "Jane"].map(&hi) #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey) #=> ["Hey, Bob!", "Hey, Jane!"] [
Создаёт блок, который будет вызывать метод с именем символа
:to_s.to_proc.call(1) #=> "1"
1, 2].map(&:to_s) #=> ["1", "2"]
[1..3).map(&:succ) #=> [2, 3, 4] (
Создаёт блок, который связывает ключи с их значением
1, b:2}
h = {a:
hash_proc = h.to_proc:a) #=> 1
hash_proc.call(:b) #=> 2
hash_proc.call(:c) #=> nil
hash_proc.call(:a, :b, :c].map(&h) #=> [1, 2, nil] [
Метод Object#method(symbol)
позволяет создать объект класса Method
, который позволяет вызывать данный метод в стиле лямбды
Интерфейс класса Method
повторяет в ключевых моментах интерфейс класса Proc
и де-факто может использоваться как альтернатива блокам и лямбдам
class Thing
def square(n)
n*nend
end
Thing.new
thing = :square)
square_method = thing.method(9) #=> 81
square_method.call(1, 2, 3 ].map(&square_method) #=> [1, 4, 9] [
Классы Proc
и Method
предоставляют методы <<
и >>
, которые позволяют создать цепочки обработки данных
multiplication = lambda {|x| x * x }
addition = lambda {|x| x + x }
2) #=> 16
(multiplication << addition).call(2) #=> 8 (multiplication >> addition).call(
Ограничением такого подхода является то, чтобы формат передаваемых данных был в точности тем, что ожидают блоки
2).then do |mult|
result = multiplication.call(
addition.call(mult)end
#=> 8 puts result
Объекты классов Proc
и Method
поддерживают метод curry
, который позволяет создавать блоки с предустановленными аргументами
summator = lambda { |x, y, z| x + y + z }1).call(2).call(3) #=> 6
summator.curry.call(
apply_math = -> (fn, a, b) { a.send(fn), b }
add = apply_math.curry.(:+)1, 2) # => 3
add.(1)
increment = add.curry.(1) # => 2
increment.(5) # => 6 increment.(