Ruby is Magic - Episode #7: Closures

Post on 29-Nov-2014

1.552 views 0 download

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

Ruby

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

7

Blöcke

@my_bag = Bag.new

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

@my_bag.each_item ?

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

@my_bag.each_item { |item| puts item }

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

Werden an Methoden übergeben

Fangen den definierenden Kontext ein

Erweitern den definierenden Kontext nicht

Können nicht

herumgereicht oder

jederzeit aufgerufen werden

Blöcke

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 }

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!

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

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

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

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)

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

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

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

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

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

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?

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

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

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