Ruby is Magic: Closures

Ruby is Magic: Closures

Talk (in German) about Ruby's closures at the Ruby user group Cologne (01/2012)

19f1245e673133f1f5b36a1a658f8c1d?s=128

Sebastian Cohnen

June 08, 2012
Tweet

Transcript

  1. Bronie User Group Cologne

  2. Ruby #7 “Closures”

  3. 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, …
  4. 1 2 3 4 5 6 7

  5. Blöcke @my_bag = Bag.new %w(MacBook Headphones iPhone Camera).each do |item|

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

    end end @my_bag.each_item { |item| puts item }
  7. %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’
  8. Werden an Methoden übergeben Fangen den definierenden Kontext ein Erweitern

    den definierenden Kontext nicht Können nicht herumgereicht oder jederzeit aufgerufen werden Blöcke
  9. 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 }
  10. 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!
  11. “echte” Closures? &block ohne & ist wie Proc.new(&block) proc {}

    lambda {}
  12. Kontrollfluss & Aritätsprüfung

  13. 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)
  14. 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!" })
  15. 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.’
  16. 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)
  17. 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: Proc
  18. 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 Aritätsprüfung: Lambda
  19. 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
  20. Beispiel: Lazy Collection

  21. 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
  22. block (implizit übergeben) block (explizit übergeben) block (explizit übergeben und

    zu Proc) Proc.new proc (Alias auf lambda / Proc.new) lambda 6 Möglichkeiten
  23. One More Thing …

  24. None
  25. method()

  26. 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)
  27. 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
  28. class CacheDecorator < BaseDecorator def call(*args) puts "Before closure" result

    = @closure.call(*args) puts "After closure" return result end end
  29. 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?
  30. 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
  31. class BaseDecorator def bind_to(receiver) @closure = @closure.bind(receiver) end end

  32. 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 Pony” © Hasbro Studios and DHX Media Vancouver