Подтвердить что ты не робот

Как я могу вызвать 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
4b9b3361

Ответ 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