Bienvenidos sean a este post, hasta hoy hemos visto que Lua es un lenguaje interpretado pero en realidad el lenguaje precompila el codigo a una forma intermedia antes de ejecutarlo, y aunque esto puede sonar como a fuera de lugar en realidad esta caracteristica distintiva de lenguaje interpretado no significa que no sea compilado sino que el compilador es parte de las rutinas del lenguaje y esto nos permite de una manera mas facil ejecutar el codigo “on the fly” (al vuelo).

Anuncios
Nota: Esta forma de trabajar es realizada por muchos lenguajes de tipo interpretado.

En otros posts anteriores vimos a dofile como una especie de operacion primitiva para correr chunks en el codigo Lua pero en realidad dofile es una funcion auxiliar, ya que loadfile es en realidad la que hace el trabajo. Al igual que dofile, loadfile carga un chunk de Lua desde un archivo pero no lo ejecuta, solamente compila el chunk y devuelve el chunk compilado como una funcion, al igual que dofile, loadfile no maneja errores pero si nos devuelve codigos de error, lo cual nos permite manejar los errores, podemos definir a dofile de la siguiente manera:

function dofile(archivo)
	local f = assert(loadfile(archivo))
	return f()
end

En este caso utilizamos a assert por si ocurre un error con loadfile, con esto podemos decir que para tareas simples podemos usar a dofile porque hace la tarea completa, en cambio loadfile es un poco mas flexible y ante un eventual error nos devuelve un valor nil ademas del mensaje de error permitiendo manejar el error de maneras mas practicas y a su vez si necesitamos ejecutar un archivo varias veces, podemos llamar a loadfile una vez y llamar al resultado las veces necesarias, esto es mucho mas practico que llamar varias veces a dofile porque el archivo es compilado una sola vez.

La funcion load es similar a loadfile, pero en lugar de leer el chunk desde un archivo lo hace desde una cadena (String), veamos el siguiente codigo:

f = load("i = i + 1")

Si lo probamos obtendremos un resultado como este:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> f = load("i = i + 1")
> i = 0
> f(); print(i)
1
> f(); print(i)
2
>

En este caso podemos ver como f contendra la expresion informada en la primera linea y al llamarla nuevamente incrementara el valor de i, como podemos ver esta funcion es muy poderosa por lo que deberia usarse con cuidado, porque al ser una funcion importante puede resultar en una codigo incomprensible, antes de usarlo deberias verificar que no exista una manera mas simple de resolver el problema. ahora si en lugar de esto quieres hacer un “dostring” de manera rapida y sucia podrias llamar al resultado directamente desde el load:

Anuncios
load(i)()

Esto cargara y ejecutara el chunk, aunque si tenemos algun error de sintaxis puede devolvernos el siguiente error:

stdin:2: attempt to call a nil value

Aunque si queremos un mejor mensaje de error podemos usar el siguiente metodo:

assert(load(i))()

Donde podremos tener esta respuesta:

stdin:1: [string "2"]:1: unexpected symbol near '2'

Usualmente no usaremos una cadena literal con load, por ejemplo el primero ejemplo que vimos de load equivaldria al siguiente codigo:

f = function() i = i + 1 end

Y en este caso tendriamos una mejor performance porque el codigo se compila una sola vez cuando el chunk es compilado, en cambio con load siempre involucra una nueva compilacion, pero a pesar de lo que creemos estos dos complejos no son equivalentes, para notar la diferencia apliquemos el siguiente ejemplo:

Anuncios
tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> do
>> i = 32
>> local i = 0
>> f = load("i = i + 1; print(i)")
>> g = function() i = i + 1; print(i) end
>> f()
>> g()
>> end
33
1
>

Como pueden ver la funcion g utilizo a la i local en cambio la funcion f utilizo la variable i global porque load siempre compila sus cadenas con el entorno global, esto significa que load es usualmente mas utilizado para correr codigo externo o piezas de codigo que vienen de afuera de tu programa, un ejemplo seria: trazar una funcion definida por el usuario, el usuario ingresa la funcion y utilizas load para evaluar a la misma porque load espera un chunk que equivale a la declaracion,, en el caso de querer evaluar una expresion, debemos utilizar como prefijo a return asi obtienes una declaracion que devuelve el valor de la expresion dada, veamos el siguiente ejemplo:

load.lua

print "Ingresa tu funcion: "
l = io.read()
func = assert(load("return l"))
print("El valor de tu expresion es " .. func())

Este codigo espera porque ingreses una funcion, la cual almacenara en l, despues en el load entre las comillas usaremos a return junto a nuestra variable donde ingresamos la expresion a evaluar, por ultimo mostraremos el valor almacenado en func, veamos su salida:

Anuncios
tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 load.lua
Ingresa tu funcion:
print("Hola, mundo!")
El valor de tu expresion es print("Hola, mundo!")
tinchicus@dbn001dsk:~/lenguaje/lua$

Observen como en lugar de mostrar:

return print("Hola, mundo!")

nos mostro la expresion ingresada, si en lugar de usar el archivo hubieramos usado el interprete hubieramos tenido la siguiente salida:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> do
>> print "Ingresa tu funcion: "
>> l = io.read()
>> func = assert(load("return l"))
>> print("El valor de tu expresion es " .. func())
>> end
Ingresa tu funcion:
Hola, Mundo!
El valor de tu expresion es Hola, Mundo!
>

Como dijimos antes load se mueve unicamente en el ambito global, pero lo bueno de trabajar de esta forma es que podemos reutilizar esta funcion, veamos el siguiente ejemplo:

load2.lua

print("Ingresa un valor para ser trazado (con variable 'x'):")
l = io.read()
f = assert(load("return l"))
for i=1,20 do
        x = i
        print(string.rep("*",f()))
end

Este codigo es muy similar al anterior pero en lugar de agregar una cadena debemos agregar un valor, el cual sera almacenado en l, para este ejemplo f hace exactamente lo mismo que func en el caso anterior, luego tendremos un bucle que contara de 1 a 20, tendra una x de tipo global para que pueda ser vista desde el chunk, despues usaremos a rep, que es parte de string, el cual se encargara de repetir el caracter que informemos, en este caso sera el asterisco (*) y luego por medio de f le diremos la cantidad de veces que se repiten, porque f retornara el valor ingresado en l, para entender mejor el concepto probemos el programa y veamos su salida:

Anuncios
tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 load2.lua
Ingresa un valor para ser trazado (con variable 'x'):
20
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
tinchicus@dbn001dsk:~/lenguaje/lua$

En este caso ingresamos el valor 20 y este ese el numero que repetimos los asteriscos por linea y hace 20 lineas, como podran darse cuenta, solamente fue necesario crearlo una vez para que fuera rellamado la cantidad de veces necesarias por nosotros, en este caso podemos decir que load utiliza una funcion de lectura que se encarga de recibir el chunk para procesarlo, este a su vez lo devuelve en partes, y load es llamado hasta que devuelve un nil, que simboliza el final del chunk, esto quiere decir que load es utilizado cuando tenemos un chunk no es parte del archivo (p.e. generados dinamicamente o desde otra fuente). o es demasiado grande para encajar en memoria.

Lua trata a cada chunk independiente como el cuerpo de una funcion anonima con un numero variable de argumentos, por ejemplo:

load("a = i")

Equivale a:

function (…) a = i end

Como cualquier funcion, los chunks pueden declarar variables locales:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> f = load("local a = 10; print(a + 20)")
> f()
30
>

Como ven en el caso anterior, al llamar a la funcion f nos realizo la operacion correctamente al incrementar el valor de la variable local a, esta caracteristica nos permite reescribir el caso anterior para evitar el uso de x, veamos la modificacion que debemos realizar:

load2.lua

print("Ingresa un valor para ser trazado (con variable 'x'):")
l = io.read()
f = assert(load("local x=...; return l"))
for i=1,20 do
        print(string.rep("*",f(i)))
end
tinchicus@dbn001dsk:~/lenguaje/lua$
Anuncios

En este caso creamos a x como una variable local del chunk junto a varargs, luego la siguiente modificacion es a la hora de llamar a f donde le enviaremos a i para que complete varargs, como dijimos antes load no maneja los errores sino que nos devolvera un valor nil con informacion adicional sobre el error:

> print(load("i i"))
nil     [string "i i"]:1: syntax error near 'i'

Ademas estas funciones no tienen un efecto secundario, solo compilan el chunk a una representacion interna y devuelve el resultado como una funcion anonima, un error comun es asumir que cargar un chunk define la funcion, las funciones son definiciones como tales y son hechas en el tiempo de ejecucion, no en el tiempo de compilacion, en el siguiente ejemplo vamos a suponer que tenemos un archivo llamado foo.lua con el siguiente codigo:

function foo(x)
	print(x)
end

Luego ejecutamos este comando:

f = loadfile("foo.lua")

Despues de este comando se compilo a foo pero no se definio todavia, para definirlo debemos ejecutar el chunk:

print(foo)	=>	nil
f()		=>	Se definio el chunk
foo("ok")	=>	ok

Como pueden ver en el primer caso tenemos un nil porque la funcion no fue definida, cuando usamos a f define a nuestra funcion y por ultimo podemos usarlo libremente.

Anuncios

En resumen, hoy hemos visto como es la compilacion en LUA, sus distintos pasos, algunos de sus metodos o funciones, como trabaja realmente, como nos beneficia, algunas cosas a tener en cuenta para implementarlo y para que deberia ser utilizado, 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