Как я могу вызвать Proc, который принимает блок в другом контексте?
Возьмем этот пример Proc:
proc = Proc.new {|x,y,&block| block.call(x,y,self.instance_method)}
Требуется два аргумента: x и y, а также блок.
Я хочу выполнить этот блок, используя разные значения для себя. Что-то вроде этого почти срабатывает:
some_object.instance_exec("x arg", "y arg", &proc)
Однако это не позволяет вам проходить в блоке. Это также не работает.
some_object.instance_exec("x arg", "y arg", another_proc, &proc)
и
some_object.instance_exec("x arg", "y arg", &another_proc, &proc)
Я не уверен, что еще может работать здесь. Возможно ли это, и если да, то как вы это делаете?
Изменить: В принципе, если вы можете передать этот файл rspec, изменив метод change_scope_of_proc
, вы решили мою проблему.
require 'rspec'
class SomeClass
def instance_method(x)
"Hello #{x}"
end
end
class AnotherClass
def instance_method(x)
"Goodbye #{x}"
end
def make_proc
Proc.new do |x, &block|
instance_method(block.call(x))
end
end
end
def change_scope_of_proc(new_self, proc)
# TODO fix me!!!
proc
end
describe "change_scope_of_proc" do
it "should change the instance method that is called" do
some_class = SomeClass.new
another_class = AnotherClass.new
proc = another_class.make_proc
fixed_proc = change_scope_of_proc(some_class, proc)
result = fixed_proc.call("Wor") do |x|
"#{x}ld"
end
result.should == "Hello World"
end
end
Ответы
Ответ 1
Чтобы решить эту проблему, вам необходимо повторно связать Proc с новым классом.
Здесь ваше решение, используя хороший код из Rails core_ext:
require 'rspec'
# Same as original post
class SomeClass
def instance_method(x)
"Hello #{x}"
end
end
# Same as original post
class AnotherClass
def instance_method(x)
"Goodbye #{x}"
end
def make_proc
Proc.new do |x, &block|
instance_method(block.call(x))
end
end
end
### SOLUTION ###
# From activesupport lib/active_support/core_ext/kernel/singleton_class.rb
module Kernel
# Returns the object singleton class.
def singleton_class
class << self
self
end
end unless respond_to?(:singleton_class) # exists in 1.9.2
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
singleton_class.class_eval(*args, &block)
end
end
# From activesupport lib/active_support/core_ext/proc.rb
class Proc #:nodoc:
def bind(object)
block, time = self, Time.now
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
method
end.bind(object)
end
end
# Here the method you requested
def change_scope_of_proc(new_self, proc)
return proc.bind(new_self)
end
# Same as original post
describe "change_scope_of_proc" do
it "should change the instance method that is called" do
some_class = SomeClass.new
another_class = AnotherClass.new
proc = another_class.make_proc
fixed_proc = change_scope_of_proc(some_class, proc)
result = fixed_proc.call("Wor") do |x|
"#{x}ld"
end
result.should == "Hello World"
end
end
Ответ 2
Я не думаю, что вы можете это сделать, и проблема не в том, чтобы передавать несколько блоков. Proc и блоки замыкаются и фиксируют привязки в момент создания. self
является частью этой привязки, поэтому, даже если вы меняете self с помощью instance_eval
, когда вы call
выполняете proc/block в своей привязке, с закрытием self:
$ irb
irb(main):001:0> class Foo; def mkproc; Proc.new { puts "#{self.class}:#{object_id}" }; end; end
=> nil
irb(main):002:0> p = Foo.new.mkproc
=> #<Proc:[email protected](irb):1>
irb(main):003:0> p.call
Foo:14164520
=> nil
irb(main):004:0> 'bar'.instance_exec { puts "#{self.class}:#{object_id}"; p.call }
String:16299940
Foo:14164520
Ruby позволит вам зафиксировать закрытие Binding с Ядро # привязка, но не предлагает установить привязку, связанную с Proc. Вы можете указать привязку для строковой версии Kernel # eval, но это все еще не позволяет вам изменять привязку обработанного вами proc.
irb(main):005:0> class BindMe; def get_binding(p=nil); binding; end; end
=> nil
irb(main):006:0> b = BindMe.new.get_binding(p)
=> #<Binding:0x00000001f58e48>
irb(main):007:0> eval '"#{self.class}:#{object_id}"', b
=> "BindMe:14098300"
irb(main):008:0> eval '"#{self.class}:#{object_id}"', p.binding
=> "Foo:14164520"
irb(main):009:0> eval "p.call", b
Foo:14164520