Nota: Este post se aplica a versiones de LUA 5.1 o inferiores.
Anuncios

Bienvenidos sean a este post, uno de los problemas con el entorno es que es global y cualquier modificacion que realices afecta a todo el programa, un ejemplo es cuando instalamos una metatabla para controlar el acceso global, todo tu programa debe seguir las lineas guias y si quieres usar una libreria que usa variables globales sin declararlas estas de mala suerte, pero LUA 5 mejoro este problema permitiendo a cada funcion a tener su propio entorno en donde busca por variables globales, esta facilidad podria sonar extraña a la primera porque despues de todo la meta para una tabla de variables globales es ser global ¯\_(ツ)_/¯ sin embargo mas adelante veremos como esta facilidad permite varias construcciones interesantes donde variables globales estan aun disponibles en todos lados.

El entorno de una funcion puede ser cambiado con la funcion setfenv (set function environment) que toma como argumentos la funcion y el nuevo entorno, en lugar del nombre de la funcion misma puedes tambien darle un numero que simboliza la funcion activa en el nivel de la pila informada, para entender mejor este concepto los numeros de la siguiente forma:

  • Numero 1, la funcion actual
  • Numero 2, la funcion que llama a la funcion del numero 1
  • Numero 3, la funcion que llama a la funcion del numero 2 que a su vez llama a la del numero1
Anuncios

Y a si sucesivamente con todas las funciones que siguen, un intento inocente de llamar a setfenv puede fallar miserablemente como se ve en el siguiente codigo:

a = 1
setfenv(1, {})
print(a)

Dando como resultado:

stdin 5: attempt to call global 'print' (a nil value)

Este codigo debe correrse un chunk simple si entras linea por linea en modo interactivo cada linea es una funcion diferente y la llamada a setfenv afecta solo a su linea, una vez que cambia tu entorno todos los accesos globales usaran la nueva tabla, si esta vacia has perdido todas tus variables globales, inclusive _G, asi que lo primero que deberias hacer es llenarla con algunos valores utiles tal como el viejo entorno:

a = 1			// Crea una variable global
setfenv(1, {g = _G})	// Cambia el entorno actual
g.print(a)		// Devuelve nil
g.print(g.a)		// Devuelve 1
Anuncios

Cuando accedemos al g global su valor es el entorno viejo en donde encontraras el campo print, tambien podemos reescribir el ejemplo anterior reemplazando a g por _G:

setfenv(1, {_G = _G})	// Cambia el entorno actual
g.print(a)		// Devuelve nil
g.print(_G.a)		// Devuelve 1

Para Lua _G es un nombre como cualquier otro, su unico estado especial sucede cuando Lua crea la tabla global inicial y asigna esta tabla a la variable global _G, Lua no le interesa sobre el valor actual de esta variable; setfenv no lo seteara en nuevos entornos pero esta acostumbrada usar este mismo nombre cuando tengamos una referencia a la tabla global inicial, como hicimos con el ejemplo reescrito, otra forma de rellenar tu nuevo entorno es con herencia:

a = 1
local newgt = {}			// crea nuevo entorno
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)			// Lo setea
print(a)				// Devuelve 1

En este codigo el nuevo entorno hereda a ambos, print y a, desde el viejo sin embargo cualquier asignacion va a la nueva tabla y no hay ningun peligro de cambiar realmente una variable global por error aunque aun puedes cambiarlo a traves de _G:

Anuncios
-- continua del codigo previo

a = 10
print(a)		// Devuelve 10
print(_G.a)		// Devuelve 1
_G.a = 20
print(_G.a)		// Devuelve 20

Cada funcion, o mejor dicho cada cierre, tiene un entorno independiente, el proximo chunk ilustra este mecanismo:

function fabrica()
	return function()
		  return a	// Devuelve "a global"
		end
end

a = 3

f1 = fabrica()
f2 = fabrica()
print(f1())			// Devuelve 3
print(f2())			// Devuelve 3

setfenv(f1, { a=10 })
print(f1())			// Devuelve 10
print(f2())			// Devuelve 3
Anuncios

La funcion fabrica crea cierres simples que devuelven el valor de su a global, cada llamada a fabrica crea un nuevo cierre con su propio entorno, siempre que creas un nueva funcion esta heredara su entorno de la funcion que la genera asi que cuando creamos los cierres estos comparten el entorno global donde el valor de a es 3, la llamada a setfenv cambia el entorno de f1 a uno nuevo donde el valor de a es 10 sin afectar el entorno de f2, debido a que las nuevas funciones heredan sus entornos de la funcion creadora si un chunk cambia su propio entorno todas las funciones que se definan a partir de esta compartiran este nuevo entorno, este es un mecanismo util para crear espacio de nombres, tambien conocidos como namespaces, lo cual veremos mas adelente.

Anuncios

En resumen, hoy hemos visto como es un entorno global, setfenv, el cual es utilizado hasta la version 5.1 de LUA, como nos permite trabajar con varios entornos globales, como cada funcion puede mantenerse independiente una de la otra, 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