在不改变其上下文的情况下在块中使用方法?

时间:2014-08-03 16:17:04

标签: ruby

我想创建一个执行以下操作的类:

  • 其实例接受一个块。
  • 在实例初始化期间,它执行某些操作,然后调用块,然后执行更多操作。
  • 在该块中,该类的另一种方法应该可用。

以下是我希望它如何运作:

Foo.new do
  puts "Hell, I can get you a toe by 3 o'clock this afternoon..."
  bar
  puts "...with nail polish."
end

我已设法通过以下课程实现它:

class Foo

  def initialize(&block)

    puts "This represents a beginning action"

    instance_eval &block

    puts "This symbolizes an ending action"

  end

  def bar
    puts "I should be available within the block."
  end

end

如您所见,我使用instance_eval技巧。它允许在块中使用bar

它工作正常,但问题是instance_eval使当前本地上下文不可用。如果我在另一个类中使用它,我将无法访问该类的方法。例如:

class Baz
  def initialize
    Foo.new do
      bar  # -> Works
      quux # -> Fails with "no such method"
    end
  end

  def quux
    puts "Quux"
  end
end

问题是:如何允许在块中执行bar而不会失去对quux的访问权限?

我的新手心灵的唯一方法是将bar作为参数传递到块中。但这需要更多的打字,所以我想尽可能避免这种情况。

2 个答案:

答案 0 :(得分:3)

instance_eval没有考虑调用块的范围,因此每个方法调用只相对于Foo中定义的内容。

所以你有两个选择。任

def initialize
  baz = self
  Foo.new do
    bar  # -> Works
    baz.quux # -> Works
  end
end

def initialize
  puts "This represents a beginning action"
  yield self
  puts "This symbolizes an ending action"
end

....

def initialize
  Foo.new do |b|
    b.bar  # -> Works too
    quux # -> Works too
  end
end

我不确定哪一个会更好的表现,但你选择的选项是基于你自己的偏好。

答案 1 :(得分:3)

  

它工作正常,但这里的问题是instance_eval使   当前本地环境不可用

instance_eval()没有这样的事情。 所有块内的代码,即看起来像:

{ code here }

可以看到块创建时周围范围中存在的变量。在块执行时,块无法查看周围范围中的变量。在计算机科学术语中,一个块被称为闭包,因为它在创建时“关闭”了周围范围内的变量。

instance_eval 做了什么是为块关闭的自变量赋值。这是一个例子:

puts self  #=>main
func = Proc.new {puts self}  
func.call  #=>main

class Dog
  def do_stuff(f)
    puts self
    f.call
  end
end

d = Dog.new
d.do_stuff(func) 

--output:--
#<Dog:0x000001019325b8>
main   #The block still sees self=main because self was equal to main when the block was created and nothing changed the value of that self variable

现在使用instance_eval:

class Dog
  def do_stuff(f)
    puts self
    instance_eval &f
  end
end

d = Dog.new
d.do_stuff(func) 

--output:--
#<Dog:0x000001011425b0>
#<Dog:0x000001011425b0>  #instance_eval() changed the value of a variable called self that the block `closed over` at the time the block was created

您还需要意识到,当您调用方法而未指定“接收方”时,例如

quux()

...然后ruby将该行转换为:

self.quux()

因此,了解变量self的值非常重要。检查此代码:

class Dog
  def do_stuff(f)
    puts self  #Dog_instance
    instance_eval &f  #equivalent to self.instance_val &f, 
                      #which is equivalent to Dog_instance.instance_eval &f
  end
end

因为instance_eval()将块内的self变量的值设置为instance_eval()的'receiver',所以块内的self值设置为等于Dog_instance。

在此处检查您的代码:

puts self #=> main

Foo.new do
  puts self  #=>main

  bar  #equivalent to self.bar--and self is not a Foo or Baz instance
       #so self cannot call methods in those classes  
end

在此处检查您的代码:

class Foo
  def initialize(&block)
    instance_eval &block  #equivalent to self.instance_eval &block
  end
end

在Foo#initialize()里面,self等于新的Foo实例。这意味着在块内部self被设置为等于Foo实例,因此如果你在块中写下以下内容:

quux()

这相当于:

self.quux()

相当于:

Foo_instance.quux()

这意味着必须在Foo中定义quux()。

在这个回答中:

class Baz
  def initialize
    puts self  #=>Baz_instance

    baz = self

    Foo.new do
      bar  # -> Works
      baz.quux # -> Works
    end

  end

  def quux
    puts "Quux"
  end
end

b = Baz.new

...... bar和baz线似乎有相同的'接收器':

   puts self  #=>Baz_instance

   baz = self  #To evaluate that assignment ruby has to replace the variable self 
               #with its current value, so this is equivalent to baz = Baz_instance
               #and baz no longer has any connection to a variable called self.

   Foo.new do
      bar  #=> equivalent to self.bar, which is equivalent to Baz_instance.bar
      baz.quux  #=> equivalent to Baz_instance.quux
    end

但是当instance_eval()执行该块时,它是do和end之间的所有内容,instance_eval()会改变self的值:

   Foo.new do #instance_eval changes self inside the block so that self = Foo_instance
      bar  #=> equivalent to self.bar which is now equivalent to Foo_instance.bar
      baz.quux  #=> the block still sees baz = Baz_instance, so equivalent to Baz_instance.bar
    end