Ruby is Magic - Episode #7: Closures

30
Ruby #7 “Closures”

description

In dieser Ausgabe von 'Ruby is Magic' stellen wir euch Closures in Ruby einmal etwas genauer vor.

Transcript of Ruby is Magic - Episode #7: Closures

Page 1: Ruby is Magic - Episode #7: Closures

Ruby

#7“Closures”

Page 2: Ruby is Magic - Episode #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, …

Page 3: Ruby is Magic - Episode #7: Closures

7

Page 4: Ruby is Magic - Episode #7: Closures

Blöcke

@my_bag = Bag.new

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

@my_bag.each_item ?

Page 5: Ruby is Magic - Episode #7: Closures

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

@my_bag.each_item { |item| puts item }

Page 6: Ruby is Magic - Episode #7: Closures

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

Page 7: Ruby is Magic - Episode #7: Closures

Werden an Methoden übergeben

Fangen den definierenden Kontext ein

Erweitern den definierenden Kontext nicht

Können nicht

herumgereicht oder

jederzeit aufgerufen werden

Blöcke

Page 8: Ruby is Magic - Episode #7: Closures

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 }

Page 9: Ruby is Magic - Episode #7: Closures

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!

Page 10: Ruby is Magic - Episode #7: Closures

“echte” Closures?

&block ohne & ist wie Proc.new(&block)

proc {}

lambda {}

Page 11: Ruby is Magic - Episode #7: Closures

Kontrollfluss&

Aritätsprüfung

Page 12: Ruby is Magic - Episode #7: Closures

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)

Page 13: Ruby is Magic - Episode #7: Closures

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

Page 14: Ruby is Magic - Episode #7: Closures

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

Page 15: Ruby is Magic - Episode #7: Closures

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)

Page 16: Ruby is Magic - Episode #7: Closures

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

Page 17: Ruby is Magic - Episode #7: Closures

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

Page 18: Ruby is Magic - Episode #7: Closures

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

Page 19: Ruby is Magic - Episode #7: Closures

Beispiel: Lazy Collection

Page 20: Ruby is Magic - Episode #7: Closures

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

Page 21: Ruby is Magic - Episode #7: Closures

block (implizit übergeben)

block (explizit übergeben)

block (explizit übergeben und zu Proc)

Proc.new

proc (Alias auf lambda / Proc.new)

lambda

6 Möglichkeiten

Page 22: Ruby is Magic - Episode #7: Closures

One More Thing …

Page 23: Ruby is Magic - Episode #7: Closures

method()

Page 24: Ruby is Magic - Episode #7: Closures

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)

Page 25: Ruby is Magic - Episode #7: Closures

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

Page 26: Ruby is Magic - Episode #7: Closures

class CacheDecorator < BaseDecorator

def call(*args) puts "Before closure" result = @closure.call(*args) puts "After closure"

return result end

end

Page 27: Ruby is Magic - Episode #7: Closures

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?

Page 28: Ruby is Magic - Episode #7: Closures

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

Page 29: Ruby is Magic - Episode #7: Closures

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

Page 30: Ruby is Magic - Episode #7: Closures

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