从proc设置方法局部变量

时间:2014-06-08 13:23:32

标签: ruby closures metaprogramming proc

如果我有一个包含两个实例变量@x@y的类,我可以使用self.instance_exec从proc更改它们:

class Location
  attr_accessor :x, :y
  def initialize
    @x = 0
    @y = 0
  end
  def set &block
    self.instance_exec(&block)
  end
end

location = Location.new
location.set do
  @x = rand(100)
  @y = rand(100)
end

puts location.x
puts location.y

如果我有一个方法set的类有两个局部变量xy,我可以使用proc返回值来设置它们:

class Location
  def set &block
    x = 0;
    y = 0;
    x, y = block.call()
    # do something with x and y
    puts x
    puts y
  end
end

location = Location.new
location.set do
  x = rand(100)
  y = rand(100)
  [x, y]
end

有没有办法在不使用返回值的情况下从proc访问set方法局部变量xy

1 个答案:

答案 0 :(得分:2)

你可以这样做,但它不是很漂亮

块有一种方法可以在调用方法中设置变量,但它并不漂亮。您可以传入绑定,然后使用绑定来评估一些代码:

def foo(binding)
  binding.eval "x = 2"
end

x = 1
foo(binding)
p x     # => 2

块还带有定义它们的绑定,因此如果正在传递块,则:

def foo(&block)
  block.binding.eval "x = 2"
end

x = 1
foo {}
p x     # => 2

在这种情况下,块中的内容无关紧要。它只是被用作绑定的载体。

实现相同目标的其他方式

产生一个物体

一个块与其调用者交互的更多步行方式是将一个对象传递给该块:

class Point
  attr_accessor :x
  attr_accessor :y
end

class Location

  def set
    point = Point.new
    yield point
    p point.x    # => 10
    p point.y    # => 20
  end

end

location = Location.new
location.set do |point|
  point.x = 10
  point.y = 20
end

这通常比较高级的技术更受欢迎:它很容易理解它的实现和使用。

instance_eval一个对象

如果你想(but you probably shouldn't want to),块的调用者可以使用instance_eval / instance_exec来调用块。这会将 self 设置为该块的对象。

class Location

  def set(&block)
    point = Point.new
    point.instance_eval(&block)
    p point.x    # => 10
    p point.y    # => 20
  end

end

location = Location.new
location.set do
  self.x = 10
  self.y = 20
end

你看到块在调用编写器时必须使用use self.,否则会声明新的局部变量,这不是这里想要的。

Yield instance_eval一个对象

即使你still probably shouldn't use instance_eval,有时它也很有用。但是,你并不总是知道它什么时候好,所以让我们让方法的调用者决定使用哪种技术。所有方法都要做的是检查块是否有参数:

class Location

  def set(&block)
    point = Point.new
    if block.arity == 1
      block.call point
    else
      point.instance_eval(&block)
    end
    p point.x
    p point.y
  end

end

现在,用户可以在该点的范围内执行该块:

location = Location.new
location.set do
  self.x = 10
  self.y = 20
end
# => 10
# => 20

或者它可以传递给它:

location.set do |point|
  point.x = 30
  point.y = 40
end
# => 30
# => 40