#7
       “Clos
             ures”
Ruby
Closures sind Codeblöcke, …


 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()
7
Blöcke


@my_bag = Bag.new

%w(MacBook Headphones iPhone Camera).each do |item|
  @my_bag.add item
end


            @my_bag.each_item       ?
class Bag
  def each_item
    @items.each do |item|
      yield item
    end
  end
end

@my_bag.each_item { |item| puts item }
%w(MacBook Headphones iPhone Camera).each do |item|
     item_count ||= 0
     @my_bag.add item
     item_count += 1
   end

   puts "#{item_count} item(s) have been added to my bag."




NameError: undefined local variable or method ‘item_count’
Blöcke
Werden an Methoden übergeben

Fangen den definierenden Kontext ein



Erweitern den definierenden Kontext nicht

Können nicht

  herumgereicht oder

  jederzeit aufgerufen werden
class Bag
  def initialize(items)
    @items = items
  end

  def each_item(&block)
    @items.each(&block)
  end
end

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)
  end
end

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)
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!" })




      LocalJumpError: unexpected return
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 closure
The 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)
Aritätsprüfung: Proc

proc_closure = Proc.new do |arg1, arg2|
  puts "arg1: #{arg1}; arg2: #{arg2}"
end


proc_closure.call(1,2,3,4) # arg1: 1; arg2: 2
proc_closure.call(1,2) # arg1: 1; arg2: 2
proc_closure.call(1) # arg1: 1; arg2: nil
Aritätsprüfung: Lambda

lambda_closure = lambda do |arg1, arg2|
  puts "arg1: #{arg1}; arg2: #{arg2}"
end


lambda_closure.call(1,2,3,4) # ArgumentError
lambda_closure.call(1,2) # arg1: 1; arg2: 2
lambda_closure.call(1) # ArgumentError
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)
  end
end
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) }
  end
end

class Iterator
  def self.print_element(element)
    puts "Element: #{element}"
  end
end

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
Was müssen wir machen?

Erkennen welche Methode zu dekorieren ist

Methode extrahieren

Decorator mit extrahierter Methode inititalisieren

Proxy Methode definieren

Binding vor Ausführung der “alten” Methode umsetzen
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
  end
end
class BaseDecorator
  def bind_to(receiver)
    @closure = @closure.bind(receiver)
  end
end
Thanks! Q & A?



                                  ?



                                                                                 “My Little Pony” © Hasbro Studios and DHX Media Vancouver
                        Dirk Breuer / @railsbros_dirk

                         Sebastian Cohnen / @tisba

Thanks to Paul Cantrell (http://innig.net/software/ruby/closures-in-ruby.html)

Ruby is Magic - Episode #7: Closures

  • 1.
    #7 “Clos ures” Ruby
  • 2.
    Closures sind Codeblöcke,… 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()
  • 3.
  • 4.
    Blöcke @my_bag = Bag.new %w(MacBookHeadphones iPhone Camera).each do |item| @my_bag.add item end @my_bag.each_item ?
  • 5.
    class Bag def each_item @items.each do |item| yield item end end end @my_bag.each_item { |item| puts item }
  • 6.
    %w(MacBook Headphones iPhoneCamera).each do |item| item_count ||= 0 @my_bag.add item item_count += 1 end puts "#{item_count} item(s) have been added to my bag." NameError: undefined local variable or method ‘item_count’
  • 7.
    Blöcke Werden an Methodenübergeben Fangen den definierenden Kontext ein Erweitern den definierenden Kontext nicht Können nicht herumgereicht oder jederzeit aufgerufen werden
  • 8.
    class Bag def initialize(items) @items = items end def each_item(&block) @items.each(&block) end end bag = Bag.new %w(MacBook Headphones Keys) bag.each_item { |item| puts item }
  • 9.
    class Bag def initialize(items) @items = items end def define_iterator(&block) @iterator = block # Proc.new &block end def iterate! @items.each(&@iterator) end end bag = Bag.new(%w(MacBook Headphones Keys)) bag.define_iterator { |item| puts item } bag.iterate!
  • 10.
    “echte” Closures? &block ohne& ist wie Proc.new(&block) proc {} lambda {}
  • 11.
    Kontrollfluss & Aritätsprüfung
  • 12.
    Kontrollfluss Proc.new ist abhängigvon 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)
  • 13.
    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!" }) LocalJumpError: unexpected return
  • 14.
    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 closure The result of the call was: ‘Everypony calm down. All is good.’
  • 15.
    Aritätsprüfung arity()-Methode Instanzen von Proc.newprüfen die Artität nicht Closures durch lambda prüfen die Arität (in Ruby 1.9)
  • 16.
    Aritätsprüfung: Proc proc_closure =Proc.new do |arg1, arg2| puts "arg1: #{arg1}; arg2: #{arg2}" end proc_closure.call(1,2,3,4) # arg1: 1; arg2: 2 proc_closure.call(1,2) # arg1: 1; arg2: 2 proc_closure.call(1) # arg1: 1; arg2: nil
  • 17.
    Aritätsprüfung: Lambda lambda_closure =lambda do |arg1, arg2| puts "arg1: #{arg1}; arg2: #{arg2}" end lambda_closure.call(1,2,3,4) # ArgumentError lambda_closure.call(1,2) # arg1: 1; arg2: 2 lambda_closure.call(1) # ArgumentError
  • 18.
    Fun facts In Ruby1.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
  • 19.
  • 20.
    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) end end
  • 21.
    block (implizit übergeben) block(explizit übergeben) block (explizit übergeben und zu Proc) Proc.new proc (Alias auf lambda / Proc.new) lambda 6 Möglichkeiten
  • 22.
  • 23.
  • 24.
    class Bag def each_item(closure) @items.each { |item| closure.call(item) } end end class Iterator def self.print_element(element) puts "Element: #{element}" end end 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)
  • 25.
    class DBLayer decorateCacheDecorator 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
  • 26.
    class CacheDecorator <BaseDecorator def call(*args) puts "Before closure" result = @closure.call(*args) puts "After closure" return result end end
  • 27.
    Was müssen wirmachen? Erkennen welche Methode zu dekorieren ist Methode extrahieren Decorator mit extrahierter Methode inititalisieren Proxy Methode definieren Binding vor Ausführung der “alten” Methode umsetzen
  • 28.
    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 end end
  • 29.
    class BaseDecorator def bind_to(receiver) @closure = @closure.bind(receiver) end end
  • 30.
    Thanks! Q &A? ? “My Little Pony” © Hasbro Studios and DHX Media Vancouver Dirk Breuer / @railsbros_dirk Sebastian Cohnen / @tisba Thanks to Paul Cantrell (http://innig.net/software/ruby/closures-in-ruby.html)