我怎么能调用一个在不同的上下文中取块的Proc呢?

时间:2022-11-20 23:18:54

Take this example Proc:

以这个例子Proc:

proc = Proc.new {|x,y,&block| block.call(x,y,self.instance_method)}

It takes two arguments, x and y, and also a block.

它有两个参数,x和y,还有一个block。

I want to execute that block using different values for self. Something like this nearly works:

我想用不同的self值来执行那个block。类似这样的东西几乎可以工作:

some_object.instance_exec("x arg", "y arg", &proc)

However that doesn't allow you to pass in a block. This also doesn't work

但是,这并不允许您传入一个块。这也不起作用

some_object.instance_exec("x arg", "y arg", another_proc, &proc)

nor does

也没有

some_object.instance_exec("x arg", "y arg", &another_proc, &proc)

I'm not sure what else could work here. Is this possible, and if so how do you do it?

我不知道这里还能做什么。这可能吗?如果可能的话,你怎么做呢?

Edit: Basically if you can get this rspec file to pass by changing the change_scope_of_proc method, you have solved my problem.

编辑:基本上,如果您可以通过更改change_scope_of_proc方法来传递rspec文件,那么您就解决了我的问题。

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

2 个解决方案

#1


7  

To solve this, you need to re-bind the Proc to the new class.

要解决这个问题,需要将Proc重新绑定到新类。

Here's your solution, leveraging some good code from Rails core_ext:

以下是您的解决方案,利用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's 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's 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


-2  

I don't think you can do this, and the trouble isn't passing multiple blocks. Proc's and blocks are closures and capture their bindings at the point of creation. self is part of that binding, so even if you change self with instance_eval, when you call the proc/block it executes in its binding, with the self it closed over:

我不认为你能做到这一点,麻烦的是你不能越过多个街区。Proc和块是闭包,在创建时捕获它们的绑定。self是绑定的一部分,所以即使你用instance_eval改变self,当你调用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:0x00000001b04338@(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 will let you capture a closure's Binding with Kernel#binding, but offers no way to set the binding associated with a Proc. You can specify a binding for the string version of Kernel#eval, but that still doesn't let you change the binding of a proc you call.

Ruby将允许您捕获与内核#绑定的闭包绑定,但不提供设置与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

#1


7  

To solve this, you need to re-bind the Proc to the new class.

要解决这个问题,需要将Proc重新绑定到新类。

Here's your solution, leveraging some good code from Rails core_ext:

以下是您的解决方案,利用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's 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's 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


-2  

I don't think you can do this, and the trouble isn't passing multiple blocks. Proc's and blocks are closures and capture their bindings at the point of creation. self is part of that binding, so even if you change self with instance_eval, when you call the proc/block it executes in its binding, with the self it closed over:

我不认为你能做到这一点,麻烦的是你不能越过多个街区。Proc和块是闭包,在创建时捕获它们的绑定。self是绑定的一部分,所以即使你用instance_eval改变self,当你调用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:0x00000001b04338@(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 will let you capture a closure's Binding with Kernel#binding, but offers no way to set the binding associated with a Proc. You can specify a binding for the string version of Kernel#eval, but that still doesn't let you change the binding of a proc you call.

Ruby将允许您捕获与内核#绑定的闭包绑定,但不提供设置与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