Bienvenidos sean a este post, en el post anterior vimos como utilizar entornos no globales pero para versiones de Lua igual o anteriores a la 5.1, hoy veremos como se usa para versiones mas modernas, en el post anterior vimos como trabajar con el setfenv pero en las versiones mas modernas esta funcion, junto con getfenv, fueron eliminadas y favoreciendo a la variable _ENV, si bien esta variable puede cumplir muchas de las acciones de las funciones anteriores no son reemplazos directos, hablemos sobre la variable.

Anuncios

La variable _ENV

La variable provee una forma lexica de setear el entorno de una funcion, ya que las funciones tienen un acceso a variables con ambito lexico han creado un cierre, el entorno de la funcion es seteado por el valor lexico mas cercano a _ENV, las variables referenciadas por cierres son llamados upvalues porque _ENV es solo un upvalue de una funcion que tiene el proposito especial de ser el lugar donde las referencias de las variables globales son buscadas, en orden para reimplementar setfenv y setear el entorno de la funcion se debe actualizar el upvalue para apuntar a una tabla que actue como entorno.

Obteneniendo upvalues

Los upvalues pueden ser accedidos y manipulados en Lua usando las siguientes funciones de la libreria debug:

  • debug.getupvalue
  • debug.setupvalue
  • debug.upvaluejoin

Estos valores estan indexados por un entero mas parecido a un array, para acceder a ellos se utiliza la funcion:

 debug.getupvalue(func, indice)

A continuacion veremos como trabajan las funciones para ello tendremos la siguiente funcion basica:

Anuncios
local numero = 15
local function suerte()
	print("Tu numero de la suerte: " .. numero)
end

Es una funcion simple donde tenemos una variable local llamada numero con un valor, luego una funcion llamada suerte donde lo muestra en pantalla, veamos el siguiente codigo para mostrar los upvalues:

local idx = 1

while true do
	local nombre, valor = debug.getupvalue(suerte, idx)
	if not nombre then break end
	print(nombre, valor)
	idx = idx + 1
end

Este ciclo enumera todos los valores positivos hasta que obtiene un nil por medio de debug.getupvalue, si lo probamos obtendremos una salida como esta:

Anuncios
_ENV    table: 0x2456568
numero  15

En este caso tenemos dos upvalues, _ENV y numero, en el caso de _ENV nos devolvera el valor de la variable global _G, ahora si tomamos el codigo y modificamos a numero sacandole la palabra local, y volvemos a ejecutar el codigo obtendremos el siguiente resultado:

_ENV    table: 0xd97568

Esto se debe a que numero no es un upvalue porque ahora es una variable global, cuando numero es referenciado dentro de esa funcion mirara dentro del upvalue _ENV, como se imaginan el valor esta en _ENV.numero, un ultimo ejemplo para ver es considerar una funcion vacia, cual seria el valor upvalue? Uno puede creer que es _ENV pero esto no es correcto porque la funcion solo tendra este upvalue si se referencia a una variable global, en el primer ejemplo la funcion globalmente referenciada es print, y en el segundo ejemplo se referencia a ambos, print y numero, tengan esto en mente.

Anuncios

Seteando upvalues

Para setear un upvalue debemos utilizar la funcion debug.setupvalue, su sintaxis es la siguiente:

debug.setupvalue(funcion, indice, valor)

Como mencionamos antes los valores upvalues son referenciados por su indice, en el caso de querer setear un upvalue por su nombre primero debemos encontrar su indice, veamos a continuacion una forma generalizada de devolver de forma simple el indice de upvalue por su nombre:

local function get_upvalue(fn, nombre_busq)
        local idx = 1
        while true do
                local nombre, valor = debug.getupvalue(fn, idx)
                if not nombre then break end
                if nombre == nombre_busq then
                        return idx
                end
                idx = idx + 1
        end
end

En esta funcion recibiremos dos argumentos, la funcion y el nombre a buscar, iniciaremos a idx para almacenar el indice, luego usaremos un bucle while que terminara cuando nombre sea igual a nil para ello usaremos a debug.getupvalue, el siguiente condicional verifica que el valor asignado a nombre sea igual a nombre_busq (el nombre que buscamos) y devolvemos el indice, ahora modifiquemos el codigo de la siguiente manera para poder probarlo:

Anuncios
local function get_upvalue(fn, nombre_busq)
        local idx = 1
        while true do
                local nombre, valor = debug.getupvalue(fn, idx)
                if not nombre then break end
                if nombre == nombre_busq then
                        return idx
                end
                idx = idx + 1
        end
end

local numero = 15

local function suerte()
        print("Tu numero de la suerte: " .. numero)
end

debug.setupvalue(suerte,get_upvalue(suerte, "numero"), 22)

suerte()

Como dijimos antes en setupvalue debemos pasar la funcion (suerte), el indice (lo obtenemos con get_upvalue) y por ultimo el nuevo valor a asignarle a numero, por ultimo llamamos a suerte, si lo probamos obtendremos esto:

Tu numero de la suerte: 22

Como vemos se modifico el valor de numero, ahora vayamos un paso adelante y vamos a usar a _ENV y en lugar de imprimir el numero de memoria donde esta la tabla usemos a print para mostrar un mensaje, para ello hagamos la siguiente modificacion en la linea anterior a llamar a suerte:

Anuncios
debug.setupvalue(suerte,get_upvalue(suerte, "_ENV"), {
	print = function()
			print("Sin numero de la suerte para ti!")
		end
	})

Si lo ejecutamos obtendremos este error:

lua5.3: entorno.lua:21: stack overflow

Esto es debido a que entra en un bucle infinito dentro de print porque estamos redefiniendo a print para que llame a print con nuestro nuevo entorno, el problema es que upvalues es compartido atraves de muchas funciones, cuando seteamos el upvalue en suerte realmente cambiamos cada entorno de funcion, es decir que cambiamos el entorno global para apuntar a una funcion que solo tiene que imprimirlo si print es llamado en el nivel superior en lugar de llamar a suerte y el mismo bucle infinito ocurrira porque el rango de global tambien comparte el mismo _ENV, adicionalmente cualquier llamado a una funcion global disponible, como type, fallara desde que el nuevo entorno solo provee a print, veamos otro ejemplo con la siguiente modificacion:

local function get_upvalue(fn, nombre_busq)
        local idx = 1
        while true do
                local nombre, valor = debug.getupvalue(fn, idx)
                if not nombre then break end
                if nombre == nombre_busq then
                        return idx
                end
                idx = idx + 1
        end
end

local numero = 15

local function suerte()
        print("Tu numero de la suerte: " .. numero)
end

local function suerte2()
        print("Tu otro numero: " .. numero)
end

debug.setupvalue(suerte,get_upvalue(suerte, "numero"), 22)

suerte2()
Anuncios

En este caso tenemos dos funciones que tienen como referencia al mismo upvalue llamado numero, el llamado a debug.setupvalue tiene el mismo efecto que si hubieramos escrito:

numero = 22 

Porque al estar en el mismo entorno cuando modificamos una de las funciones tambien modificara a la otra, si lo probamos obtendremos el siguiente resultado:

Tu otro numero: 22

Como pueden ver ocurrio lo que dijimos por mas que hayamos modificado la variable numero de la funcion suerte, esta tambien se modifico en suerte2 por este motivo debug.setupvalue no nos sirve mas por eso debemos pasar al siguiente paso.

Reemplazando upvalues

Para esta accion usaremos la funcion debug.upvaluejoin, la cual se encarga de reemplazar al upvalue que se hace referencia, para ello se debe especificar otra funcion y un indice de upvalue para hacer el reemplazo, con la creacion de una nueva funcion anonima para setear el _ENV que deberia ser para suerte, ocasionando que se reemplace el valor de upvalue sin afectar al resto, veamos el codigo:

Anuncios
local nuevo_env = {
	print = function()
			print("No tienes numero de la suerte!")
		end
}

debug.upvaluejoin(suerte, get_upvalue(suerte, "_ENV"),
		function() return nuevo_env end, 1)

suerte()

Si lo ejecutamos obtendremos esta salida:

No tienes numero de la suerte!
Nota: Los dos últimos argumentos hacen referencia al upvalue 1 de la función anónima. Dado que la función anónima solo tiene un upvalue, se garantiza que estará en el índice 1. No es necesario buscarla por su nombre.

Antes de finalizar veamos quedo finalmente nuestro codigo:

entorno.lua

local function get_upvalue(fn, nombre_busq)
        local idx = 1
        while true do
                local nombre, valor = debug.getupvalue(fn, idx)
                if not nombre then break end
                if nombre == nombre_busq then
                        return idx
                end
                idx = idx + 1
        end
end

local numero = 15

local function suerte()
        print("Tu numero de la suerte: " .. numero)
end

local function suerte2()
        print("Tu otro numero: " .. numero)
end

local nuevo_env = {
        print = function()
                        print("No tienes numero de la suerte!")
                end
}

debug.upvaluejoin(suerte, get_upvalue(suerte, "_ENV"),
                function() return nuevo_env end, 1)

suerte()
Anuncios

En resumen, esta es la forma de como trabaja las versiones mas modernas de Lua para poder modificar el entorno, hemos visto las tres funciones encargadas de suplantar a setfenv y getfenv, caso por caso con un breve explicacion de cada uno de ellos, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00