阻止变量与本地变量的可见性。如何通过一个参数将var名称和值传递给lambda?

时间:2013-07-28 03:13:10

标签: ruby lambda eval block visibility

这是验证列表和lambda,它验证对象:

valid_specs = ["instrumentalist", "dj", "producer", "songwriter", "teacher"]
validate_obj = lambda do |obj_name, valid|
  obj = eval obj_name
  p "no such #{obj_name} `#{obj}`" unless valid.include? obj
end

将local var传递给lambda是可以的:

spec_id = "tacher"
validate_obj.call("spec_id", valid_specs) #=> no such spec_id `tacher`

但是传递block var会导致错误:

specs = ["teacher", "songwriter", "producer"]
specs.each do |spec_id|
  valid_obj.call("spec_id", valid_specs)
end
#=>:in `eval': undefined local variable or method `spec_id' for main:Object (NameError)

对我来说这似乎不太明显。我想知道原因以及如何实现我的目标,即不通过两个参数"spec_id",spec_id传递var name和var值。

2 个答案:

答案 0 :(得分:1)

lambda没有对它的引用,因为它没有访问调用它的作用域(除非该作用域恰好与它定义的作用域相同,因为它在第一种情况,这只是一种副作用。)

授予其访问权限的唯一方法是传递调用范围的binding并在其上调用eval

f = ->(name) { eval name }
'foo'.tap { |x| f.('x') }  #=> #<NameError: undefined local variable or method `x' for main:Object>

f = ->(binding, name) { binding.eval name }
'foo'.tap { |x| f.(binding, 'x') }  #=> "foo"

正如您所说,唯一的另一种方法是将变量名称和值显式传递为两个参数。

答案 1 :(得分:0)

您基本上是在尝试这样做:

myfunc = lambda {puts eval("word"), eval("x")}

words = ["teacher", "songwriter", "producer"]

words.each do |word|
  x = 10
  myfunc.call
end

但是word和x是each()块的局部变量,因此lambda块无法看到它们。块可以看到它们的封闭范围 - 但是它们无法看到其封闭范围中包含的其他范围。这有效:

myfunc = lambda {puts eval("word"), eval("x")}

word = "hello"
x = 10

myfunc.call

--output:--
hello
10

这是一个更有趣的版本:

func1 = lambda {puts eval "x"}
func2 = lambda {puts x}
x = "hello"

func1.call
func2.call

--output:--
hello

1.rb:23:in `block in <main>': undefined local variable or method `x' for main:Object (NameError)
    from 1.rb:27:in `call'
    from 1.rb:27:in `<main>'

看起来eval()的绑定比第二个块的闭包“更大”:eval()的绑定是块外的整个封闭范围;但是由第二个块形成的闭包仅关闭创建块时封闭范围内的变量。

根据这个例子,这似乎有道理:

b = binding

myfunc = lambda {puts eval("word", b), eval("x", b)}

word = "hello"
x = 10

myfunc.call 


--output:--
hello
10

所以当你写:

myfunc = lambda {puts eval("word"), eval("x")}

word = "hello" 
x = 10

myfunc.call

...好像lambda块正在关闭一个不可见的b变量,eval()默认使用它,然后代码实际上是这样做的:b.word = "hello"; b.x = 10。这在这里的工作方式相同:

word = nil
x = nil

myfunc = lambda {puts word, x}

word = "hello"
x = 10

myfunc.call 

--output:--
hello
10

换句话说,一个块关闭变量 - 而不是值,并且块关闭的变量可以被块后面的代码改变。

  

有没有人知道另一种通过一个参数传递var name和var值的方法?

有许多ruby对象可以容纳多个数据:数组,哈希,Structs,自定义类的实例等。

valid_specs = ["instrumentalist", "dj", "producer", "songwriter", "teacher"]

validate_obj = lambda do |data|
  obj_name = data.var_name
  obj = eval obj_name, data.binding
  p "no such #{obj_name} `#{obj}`" unless valid_specs.include? obj
end

MyData = Struct.new(:var_name, :binding)
specs = ["teacher", "sweeper", "producer"]

specs.each do |spec_id|
  mydata = MyData.new("spec_id", binding)
  validate_obj.call(mydata)
end

--output:--
"no such spec_id `sweeper`"

但是这段代码也可以更简单地写成:

valid_specs = ["instrumentalist", "dj", "producer", "songwriter", "teacher"]

validate_obj = lambda do |spec_id|
  p "no such spec_id `#{spec_id}`" unless valid_specs.include? spec_id
end

specs = ["teacher", "sweeper", "producer"]

specs.each do |spec_id|
  validate_obj.call(spec_id)
end