Bienvenidos sean a este post, en el post anterior vimos a una funcion definida dentro de otra funcion dando como resultado que esta ultima tiene acceso total a las variables locales de la funcion que lo encierra, a esto lo llamamos rango lexico, recuerdan que lo mencionamos en el post anterior, aunque esta regla de visibilidad puede sonar obvia pero no es asi, esta propiedad en conjuncion con las funciones de primera clase es un poderoso concepto en lenguajes de programacion y que no es soportados por muchos, vamos a analizar el siguiente ejemplo.

Anuncios

Vamos a suponer que tenemos una lista de estudiantes y una tabla que asocia los nombres de los estudiantes a los grados, y nosotros necesitamos ordenar los nombres por grado del mas alto al mas bajo, para ello deberiamos hacer lo siguiente:

nombres = {"Enzo", "Martin", "Marta"}
grados = {Martin = 10, Enzo = 4, Marta = 7}
table.sort(nombres, function (n1, n2)
	return grados[n1] > grados[n2]
end)

En este caso tenemos la lista con los nombres, una tabla con los grados y vemos como se asignaron los nombres a un grado, luego tendremos a table.sort donde ordenaremos a nombres con una funcion anonima que comparara a la tabla con los indices basados en los nombres, ahora haremos una funcion para poder usarla:

function ordenporgrado(nombres, grados)
	table.sort(nombres, function (n1, n2)
		return grados[n1] > grados[n2]
	end)
end

Lo interesante de esta modificacion es que le concede acceso a la funcion table.sort al parametro grados que es local a la funcion que lo encierra, ordenporgrado, dentro de la funcion anonima grados no es una variable global ni local, a este tipo de variables habitualmente se las llama variables no locales o antiguamente upvariables y porque debemos considerar este tipo de variables? Para entenderlo el motivo veamos el siguiente ejemplo:

contador.lua

function nuevoContador()
        local i = 0
        return function()
                        i = i +1
                        return i
                end
end

c1=nuevoContador()

for i=1, 6 do
        print("El valor de i es: ", c1())
end

En este codigo tenemos una funcion anonima dentro de la funcion nuevoContador la cual refiere a una variable no local llamada i para mantener el contador, sin embargo al momento que llamamos a funcion anonima la variable i ya esta fuera del rango porque la funcion que creo esta variable (nuevoContador) ya ha retornado pero esto no importa porque Lua maneja correctamente esto gracias al concepto de cierre, simplemente con un cierre que es una ventaja adicional que permite acceder a las variables no locales correctamente, probemos el programa para ver su salida:

Anuncios
tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 contador.lua
El valor de i es:       1
El valor de i es:       2
El valor de i es:       3
El valor de i es:       4
El valor de i es:       5
El valor de i es:       6
tinchicus@dbn001dsk:~/lenguaje/lua$

Si llamamos a nuevoContador nuevamente este creara una nueva variable i por lo que crearemos un nuevo cierre que solo actuara solamente sobre este variable, para entender mejor este concepto hagamos la siguiente modificacion en el ejemplo anterior:

function nuevoContador()
        local i = 0
        return function()
                        i = i +1
                        return i
                end
end

c1=nuevoContador()
c2=nuevoContador()

print("El valor de c1 es: ", c1())
print("El valor de c2 es: ", c2())
print("El valor de c1 es: ", c1())
print("El valor de c1 es: ", c1())
print("El valor de c2 es: ", c2())

En este caso agregamos una nueva variable (c2) que apunta a nuevoContador y luego imprimiremos 2 veces a c2 y tres veces a c1 si lo probamos el programa ahora obtendremos esta salida:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 contador.lua
El valor de c1 es:      1
El valor de c2 es:      1
El valor de c1 es:      2
El valor de c1 es:      3
El valor de c2 es:      2
tinchicus@dbn001dsk:~/lenguaje/lua$

Como pueden ver en este caso tenemos dos cierres sobre la misma funcion y al ejecutarlo observen como se incrementa solamente cuando llamamos a cada variable respectivamente, si hablamos de valores en Lua podemos decir que es el cierre y no la funcion, la funcion en si es un prototipo de cierre, quien realmente manipula a nuestros datos y a pesar de esto seguiremos usando la palabra funcion para referirse a un cierre siempre que no haya posibilidad de confusion.

Anuncios

Este mecanismo provee una herramienta muy util para muchos contextos, por ejemplo es muy util como argumentos para funciones de orden superior como vimos con table.sort, y los cierres son importantes para funciones que crean otras funciones tambien, como vimos antes en el caso de nuevoContador, en este caso vimos un mecanismo que permite a Lua hacer programas que incorporen complejas tecnicas de programacion del mundo de lo funcional y tambien son utiles para funciones de llamadas (callbacks), un tipico ejemplo es el boton creados en una GUI con una toolkit convencional porque cada boton tiene una funcion de llamada cada vez que el usuario presiona el mismo y obviamente nosotros vamos a necesitar varios botones donde cada uno tendra su accion y por ende su funcion, para evitar crear una funcion para cada boton nosotros podemos crear una funcion que sirva para cada boton, tomemos como ejemplo una calculadora donde tenemos el teclado numerico y para las teclas podriamos usar la siguiente funcion:

function digitoBoton(digito)
	return Button{ label = tostring(digito),
			action = function()
					add_to_dsplay(digito)
				end
		}
end

Asumamos que esta funcion genera un nuevo boton, en este caso al crearlo le pondra como etiqueta (label) el digito informado como argumento a la funcion, en accion (action) usaremos una funcion anonima que agregara el digito al display que usemos, aunque este fuera de rango igualmente podremos alcanzarlo, los cierres son valiosos aun en otros contextos porque las funciones son almacenadas en variables comunes y Lua nos permite redefinir funciones facilmente, inclusive las predefinidas, esta facilidad es lo que convierte a Lua en un lenguaje tan flexible.

Anuncios

A menudo cuando redefinimos una funcion se necesita la funcion original en la nueva implementacion, por ejemplo para redefinir la funcion math.sin para operar en grados en lugar de radianes, esta nueva funcion primero convierte los argumentos recibidos de grados a radianes para luego implementar la funcion original para que haga el verdadero trabajo, el codigo seria algo similar a esto:

viejoSin = math.sin
math.sin = function(x)
	return viejoSin(x*math.pi/180)
end

Una version mas limpia de este codigo seria el siguiente:

do
	local viejoSin = math.sin
	local p = math.pi/180
	math.sin = function(x)
		return viejoSin(x*p)
	end
end

Este caso es similar al anterior pero con la diferencia de que la conversion la almacenamos en una variable y luego la utilizamos en viejoSin donde nos queda una mejor version para poder depurar ante una falla, otra opcion interesante es que mantenemos a la vieja version en una variable privada que solo puede ser accedida por la version nueva, esta tecnica nos permite crear entornos seguros, tambien llamados sandboxes, para poder correr codigos no confiables como un codigo recibido por internet a traves de un server, un ejemplo podria modificar a io.open para restringir al programa a acceder a algunos archivos por medio de cierre, veamos el siguiente ejemplo:

do
	local viejoOpen = io.open
	local accesoOk = function(archivo, modo)
		< chequeo de acceso >
	end
	io.open = function(archivo, modo)
		if (accesoOk(archivo, modo) then
			return viejoOpen(archivo, modo)
		else
			return nil, "acceso denegado"
		end
	end
end

Este ejemplo tiene la particularidad que despues de su redefinicion el programa no tendra posibilidad de llamar al viejo metodo salvo pasando por el nuevo metodo y a su vez el nuevo metodo tiene un acceso restringido para en caso de no pasar el chequeo no permitira el acceso a io.open (version vieja), este cierre permite el uso de la version vieja pero solo por la version nueva que tiene un acceso mas selectivo, como vemos con esta tecnica podemos construir sandboxes de Lua en si mismo, con la misma simplicidad, beneficios y flexibilidad, este mecanismo en lugar de proveernos una solucion que encaje en todo nos da un meta mecanismo, lo cual nos permite ajustar el entorno a la necesidad de seguridad.

Anuncios

En resumen, hoy hemos visto que son los cierres, como se utilizan, para que se utilizan, sus distintas aplicaciones, sus alcances, los accesos que concede y algunos usos practicos, 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