/ / Bloquear visibilidade de vars vs local de vars. Como passar o nome e o valor var por meio de um parâmetro para lambda? - rubi, lambda, eval, bloco, visibilidade

Bloquear Vars vs visibilidade local Vars. Como passar o nome e o valor do var através de um parâmetro para o lambda? - rubi, lambda, eval, bloco, visibilidade

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 № 1

O 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