Download - Ruby is Magic - Episode #7: Closures
Ruby
#7“Closures”
die zugewiesen und rumgereicht werden können.
die jederzeit und von jedem aufgerufen werden können.
die Zugriff auf Variablen im ursprünglich definierenden Kontext haben.
Alle antworten auf call()
Closures sind Codeblöcke, …
7
Blöcke
@my_bag = Bag.new
%w(MacBook Headphones iPhone Camera).each do |item| @my_bag.add itemend
@my_bag.each_item ?
class Bag def each_item @items.each do |item| yield item end endend
@my_bag.each_item { |item| puts item }
%w(MacBook Headphones iPhone Camera).each do |item| item_count ||= 0 @my_bag.add item item_count += 1end
puts "#{item_count} item(s) have been added to my bag."
NameError: undefined local variable or method ‘item_count’
Werden an Methoden übergeben
Fangen den definierenden Kontext ein
Erweitern den definierenden Kontext nicht
Können nicht
herumgereicht oder
jederzeit aufgerufen werden
Blöcke
class Bag def initialize(items) @items = items end def each_item(&block) @items.each(&block) endend
bag = Bag.new %w(MacBook Headphones Keys)bag.each_item { |item| puts item }
class Bag def initialize(items) @items = items end def define_iterator(&block) @iterator = block # Proc.new &block end def iterate! @items.each(&@iterator) endend
bag = Bag.new(%w(MacBook Headphones Keys))bag.define_iterator { |item| puts item }bag.iterate!
“echte” Closures?
&block ohne & ist wie Proc.new(&block)
proc {}
lambda {}
Kontrollfluss&
Aritätsprüfung
Kontrollfluss
Proc.new ist abhängig von dem definierenden Kontext
lambda verhält sich wie eine Methode (“true closure”)
proc ist ein Alias auf lambda (Ruby 1.8)
proc ist ein Alias auf Proc.new (Ruby 1.9)
LocalJumpError: unexpected return
def call_closure(closure) puts "Calling a closure" result = closure.call puts "The result of the call was: #{result}"end
call_closure(Proc.new { return "All hell breaks loose!" })
def cc(closure) puts "Calling a closure" result = closure.call puts "The result of the call was: '#{result}'"end
cc(lambda { return "Everypony calm down. All is good." })
Calling a closureThe result of the call was: ‘Everypony calm down. All is good.’
Aritätsprüfung
arity()-Methode
Instanzen von Proc.new prüfen die Artität nicht
Closures durch lambda prüfen die Arität (in Ruby 1.9)
proc_closure = Proc.new do |arg1, arg2| puts "arg1: #{arg1}; arg2: #{arg2}"end
proc_closure.call(1,2,3,4) # arg1: 1; arg2: 2proc_closure.call(1,2) # arg1: 1; arg2: 2proc_closure.call(1) # arg1: 1; arg2: nil
Aritätsprüfung: Proc
lambda_closure = lambda do |arg1, arg2| puts "arg1: #{arg1}; arg2: #{arg2}"end
lambda_closure.call(1,2,3,4) # ArgumentErrorlambda_closure.call(1,2) # arg1: 1; arg2: 2lambda_closure.call(1) # ArgumentError
Aritätsprüfung: Lambda
Fun facts
In Ruby 1.8
lambda {||}.artiy != lambda {}.arity
lambda {}.arity == -1
lambda checkt nicht die Argumente, wenn Arität 1 ist WTF!?
In Ruby 1.9
lambda {}.arity == lambda {||}.arity == 0
Beispiel: Lazy Collection
class BlogEntry class LazyLoadCollection include Enumerable
def initialize(lazy_collection, after_load_callback = nil) @lazy_collection = lazy_collection @after_load_callback = after_load_callback.present? ? after_load_callback : lambda { |args| return args } @collection = @after_load_callback.call(@lazy_collection.call) end
def each(&block) @collection.each(&block) end end
class <<self def find_all(language) lazy_feed = lambda { Nokogiri::XML(open(Rails.config.blog_feed_url)) } create_blog_entries = lambda do |feed| feed.xpath("//item").collect { |item| BlogEntry.new(xml_item) } end
LazyLoadCollection.new lazy_feed, create_blog_entries end end
def initialize(xml) self.attributes = (item.xpath("*/text()").inject({}) do |attributes, text| attributes[attribute_name] = text.content if text.parent.name.present? attributes end) endend
block (implizit übergeben)
block (explizit übergeben)
block (explizit übergeben und zu Proc)
Proc.new
proc (Alias auf lambda / Proc.new)
lambda
6 Möglichkeiten
One More Thing …
method()
class Bag def each_item(closure) @items.each { |item| closure.call(item) } endend
class Iterator def self.print_element(element) puts "Element: #{element}" endend
my_bag = Bag.new(%w(MacBook Headphones iPad Gloves))
my_bag.each_item lambda { |item| puts "Element: #{item}" }
my_bag.each_item Iterator.method(:print_element)
class DBLayer
decorate CacheDecorator def find(id) puts "Called :find with #{id}" puts "I am: #{self}" end
def destroy; end def create; end
decorate CacheDecorator def count puts "Called :count"
return 1337 end
end
class CacheDecorator < BaseDecorator
def call(*args) puts "Before closure" result = @closure.call(*args) puts "After closure"
return result end
end
Erkennen welche Methode zu dekorieren ist
Methode extrahieren
Decorator mit extrahierter Methode inititalisieren
Proxy Methode definieren
Binding vor Ausführung der “alten” Methode umsetzen
Was müssen wir machen?
module FunctionDecorators def self.apply_decorator(decorator, method_name, target) decorated_method = target.instance_method(method_name)
target.send(:remove_method, method_name)
target.__decorators[method_name] = decorator.new(decorated_method)
params = decorated_method.parameters.collect(&:last).join(',')
class_eval <<-RUBY def #{method_name}(#{params}) self.class.__decorators[:#{method_name}].bind_to(self) self.class.__decorators[:#{method_name}].call(#{params}) end RUBY endend
class BaseDecorator def bind_to(receiver) @closure = @closure.bind(receiver) endend
Thanks! Q & A?
Dirk Breuer / @railsbros_dirk
Sebastian Cohnen / @tisba
Thanks to Paul Cantrell (http://innig.net/software/ruby/closures-in-ruby.html)
?
“My
Little
Pon
y” ©
Has
bro
Stud
ios
and
DHX
Med
ia V
anco
uver