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. Bronie
    User Group
    Cologne

    View Slide

  2. Ruby
    #7
    “Closures”

    View Slide

  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, …

    View Slide

  4. 1
    2
    3
    4
    5
    6
    7

    View Slide

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

    View Slide

  6. class Bag
    def each_item
    @items.each do |item|
    yield item
    end
    end
    end
    @my_bag.each_item { |item| puts item }

    View Slide

  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’

    View Slide

  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

    View Slide

  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 }

    View Slide

  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!

    View Slide

  11. “echte” Closures?
    &block ohne & ist wie Proc.new(&block)
    proc {}
    lambda {}

    View Slide

  12. Kontrollfluss
    &
    Aritätsprüfung

    View Slide

  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)

    View Slide

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

    View Slide

  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.’

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  20. Beispiel: Lazy Collection

    View Slide

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

    View Slide

  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

    View Slide

  23. One More Thing …

    View Slide

  24. View Slide

  25. method()

    View Slide

  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)

    View Slide

  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

    View Slide

  28. class CacheDecorator < BaseDecorator
    def call(*args)
    puts "Before closure"
    result = @closure.call(*args)
    puts "After closure"
    return result
    end
    end

    View Slide

  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?

    View Slide

  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 <def #{method_name}(#{params})
    self.class.__decorators[:#{method_name}].bind_to(self)
    self.class.__decorators[:#{method_name}].call(#{params})
    end
    RUBY
    end
    end

    View Slide

  31. class BaseDecorator
    def bind_to(receiver)
    @closure = @closure.bind(receiver)
    end
    end

    View Slide

  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

    View Slide