我想创建一个执行以下操作的类:
以下是我希望它如何运作:
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
作为参数传递到块中。但这需要更多的打字,所以我想尽可能避免这种情况。
答案 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