Aqui está a lista de validação e lambda, que valida um objeto:
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
Passar var local para lambda está OK:
spec_id = "tacher"
validate_obj.call("spec_id", valid_specs) #=> no such spec_id `tacher`
Mas passar o bloco var causa um erro:
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)
Não me parece óbvio. Gostaria de saber as razões para isso e como atingir meu objetivo de não passar o nome e o valor var por meio de dois parâmetros "spec_id",spec_id
.
Respostas:
1 para resposta № 1O lambda não tem uma referência a ele porqueo não tem acesso ao escopo em que foi chamado (a menos que esse escopo seja o mesmo em que foi definido, como no primeiro caso, o que é apenas um efeito colateral).
A única maneira de dar acesso é passando pelo binding
do escopo de chamada e chamada eval
nele:
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"
A única outra maneira é, como você disse, passar explicitamente o nome da variável e o valor como dois parâmetros.
0 para resposta № 2
Você está essencialmente tentando fazer isso:
myfunc = lambda {puts eval("word"), eval("x")}
words = ["teacher", "songwriter", "producer"]
words.each do |word|
x = 10
myfunc.call
end
Mas word ex são variáveis locais de cada bloco (), então o bloco lambda não pode vê-los. Os blocos podem ver seus escopo envolvente- mas eles não podem ver dentro de outros escopos contidos em seu escopo envolvente. Isso funciona:
myfunc = lambda {puts eval("word"), eval("x")}
word = "hello"
x = 10
myfunc.call
--output:--
hello
10
Aqui está uma versão mais interessante:
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>"
Parece que a ligação para eval () é "maior"do que o fechamento para o segundo bloco: a ligação para eval () é todo o escopo de fechamento fora do bloco; mas o fechamento formado pelo segundo bloco fecha apenas sobre as variáveis que estavam no escopo envolvente no momento em que o bloco foi criado.
Isso parece fazer sentido à luz deste exemplo:
b = binding
myfunc = lambda {puts eval("word", b), eval("x", b)}
word = "hello"
x = 10
myfunc.call
--output:--
hello
10
Então, quando você escreve:
myfunc = lambda {puts eval("word"), eval("x")}
word = "hello"
x = 10
myfunc.call
... é como se o bloco lambda estivesse fechando sobre uma variável b invisível, que eval () usa por padrão, e então o código essencialmente faz isso: b.word = "hello"; b.x = 10
. Isso funciona da mesma maneira aqui:
word = nil
x = nil
myfunc = lambda {puts word, x}
word = "hello"
x = 10
myfunc.call
--output:--
hello
10
Em outras palavras, um bloco fecha sobre variáveis - não valores, e as variáveis sobre as quais um bloco fecha podem ser alteradas pelo código que vem depois do bloco.
Alguém conhece outras maneiras de passar o nome var e o valor var por meio de um parâmetro?
Existem muitos objetos ruby que podem conter mais de um dado: arrays, hashes, Structs, instâncias de classes personalizadas, etc.
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`"
Mas esse código também pode ser escrito de forma mais simples assim:
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