Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby is Magic: Closures

Ruby is Magic: Closures

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

Sebastian Cohnen

June 08, 2012
Tweet

More Decks by Sebastian Cohnen

Other Decks in Technology

Transcript

  1. 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, …
  2. class Bag def each_item @items.each do |item| yield item end

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

    den definierenden Kontext nicht Können nicht herumgereicht oder jederzeit aufgerufen werden Blöcke
  5. 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 }
  6. 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!
  7. 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)
  8. 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!" })
  9. 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.’
  10. 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
  11. 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
  12. 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
  13. 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
  14. block (implizit übergeben) block (explizit übergeben) block (explizit übergeben und

    zu Proc) Proc.new proc (Alias auf lambda / Proc.new) lambda 6 Möglichkeiten
  15. 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)
  16. 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
  17. class CacheDecorator < BaseDecorator def call(*args) puts "Before closure" result

    = @closure.call(*args) puts "After closure" return result end end
  18. 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?
  19. 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
  20. 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