Bienvenidos sean a este post, hoy hablaremos sobre una propiedad poco convencional de las funciones en Lua como es la posibilidad de devolver multiples resultados, muchas funciones predefinidas en Lua devuelven resultados multiples como por ejemplo la funcion string.find esta se encarga de ubicar un padron en una cadena, dicha funcion no solo busca el padron informado sino que nos devuelve dos indices: el indice de caracter donde comienza la coincidencia del padron y el otro donde termina, veamos el siguiente ejemplo:

Anuncios
tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> i, f = string.find("Hola LUA Mundo","LUA")
> print(i,f)
6       8
>

En este caso tenemos una simple carga de dos variables por medio de la funcion string.find donde i almacenara el primer indice y f el segundo indice que son el valor de indice del primer caracter y del ultimo caracter respectivamente, cuando usamos print para mostrarlo nos devuelve 6 y 8 que son los indices donde esta ubicada la palabra LUA, en el post de break y return hablamos de que return no solamente podia devolver un resultado sino tambien multiples y para demostrar esto usaremos el siguiente ejemplo:

function maximo (a)
        local mi = 1
        local m = a[mi]
        for i,valor in ipairs(a) do
                if valor > m then
                        mi = i
                        m = valor
                end
        end
        return m, mi
end

print(maximo({8,10,23,12,5}))

En este caso tenemos la funcion maximo donde recibira un argumento, en el bloque tendremos dos variables locales, la primera es mi con un valor inicial de 1 y esta nos servira para almacenar el indice donde esta ubicado el valor y la segunda sera para almacenar el valor de la misma (esto sera gracias a mi), despues tendremos un bucle for generico con dos variables una llamada i (para el indice) y otra llamada valor para guardar el valor, luego en el bucle tendremos un condicional if donde evaluara que el dato en valor sea mayor a m, si se cumple esta condicion pasara a guardar en mi el valor de i actual y en m el valor que esta en la variable valor, para despues seguir con el bucle y este condicional se activara siempre que valor sea mayor a m, una vez terminado el bucle for usaremos el return para devolver el valor final de m y mi, por ultimo cerramos la funcion, nuestra siguiente linea se encarga de usar un print donde contendra la funcion maximo y a este le informaremos una serie de valores entre llaves, si lo probamos al programa obtendremos la siguiente salida:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 maximo.lua
23      3
tinchicus@dbn001dsk:~/lenguaje/lua$

Observen como primero nos trajo el valor maximo de la serie de numeros y despues nos trajo en cual posicion esta ubicado, si lo desean prueben de ingresar otros numeros en la ultima serie para verificar si modifica el resultado.

Anuncios

Lua siempre ajusta el numero de los resultados de una funcion a las circunstancias de la llamada, cuando lo llamamos como una declaracion se descartan todos los resultados de la funcion, cuando es usado como una expresion este solo usa el primer resultado, y solo obtendremos todos los resultados de una funcion cuando la llamada es la ultima (o la unica) expresion en una lista de expresiones, este tipo de listas aparecen en cuatro construcciones en Lua:

  • Asignaciones multiples
  • Argumentos para llamadas de funciones
  • Constructores de tabla
  • Declaraciones de retorno

Para ilustrarlo veamos los siguientes ejemplos:

function foo0() end			<==>	No devuelve resultados
function foo1() return "a" end		<==>	Devuelve 1 resultado
function foo2() return "a", "b" end	<==>	Devuelve 2 resultados

Por ejemplo tenemos estas tres funciones, donde la primera no devuelve ningun resultado, la segunda devuelve un resultado y la tercera devuelve 2 resultados, con estas tres funciones veamos que sucede con multiples asignaciones:

x, y = foo2()		<==>	x="a", y="b"
x = foo2()		<==>	x="a" y b es descartada
x, y, z = 10, foo2()	<==>	x=10, y="a", z="b"
x, y = foo0()		<==>	x=nil, y=nil
x, y = foo1()		<==>	x="a", y=nil
x, y, z = foo2()	<==>	x="a", y="b", z=nil

Como podemos observar en estos casos siempre que haya un variable que corresponde a nuestras salidas podremos asignarlas, por ejemplo en el segundo caso como usamos a foo2 que tiene dos valores para una sola variable el segundo valor es descartado, en el tercer caso como agregamos un valor extra no se desperdicio ninguno, en cambio en los ultimos tres casos los variables que quedan fuera del rango reciben el valor nil, en el cuarto caso es porque foo0 no devuelve ningun valor, y en los dos otros casos observen como las variables que estan fuera de la cantidad de devoluciones reciben el valor nil, veamos los siguientes casos:

x, y = foo2(), 30	<==>	x="a", y=30
x, y = foo0(), 20, 30	<==>	x=nil, y=20 y el 30 es descartado
Anuncios

Observen como en el primer caso x recibe el primer valor de foo2 pero en cambio la variable y no recibe a b sino que recibe a 30, esto es por lo que hablamos antes, cuando una funcion no es la ultima o la unica solamente devuelve el primer valor, lo mismo ocurre con el segundo caso porque si bien foo no retorna ningun valor para la asignacion equivaldria a nil y por esto se lo asigna a x, luego a y le asigna 20 y por ultimo 30 es descartado.

Cuando una llamada a una funcion es el ultimo (o el unico) argumento este enviara todos los resultados como argumentos, veamos algunos ejemplos:

print(foo0())		<==>	sin valor
print(foo1())		<==>	a
print(foo2())		<==>	a	b
print(foo2(), 1)	<==>	a	1
print(foo2() .. "x")	<==>	ax

En este caso el primer ejemplo no tiene ningun valor porque foo0 no devuelve ningun valor, no confundir con nil porque no se asigna a una variable, en el segundo caso tendremos a la salida de foo1, en el tercer caso tendremos a la salida de foo2, para el cuarto caso ocurre lo que vimos antes como no es el ultimo argumento o el unico solo utiliza el primer valor y luego usa el segundo que informamos y en el ultimo caso tambien utiliza el primer valor porque es parte de una expresion y como dijimos antes si no es el ultimo solamente se utiliza el primer valor y luego lo concatena con el segundo valor, si bien print puede recibir un numero variable de argumentos, si escribimos f(g()) y f tiene un numero fijo de argumentos, Lua ajusta el numero de resultados de g al numero de argumentos de f, veamos la conducta de un constructor:

t = foo0()	<==>	t = {}
t = foo1()	<==>	t = { "a" }
t = foo2()	<==>	t = { "a", "b" }

En este caso observen como cada parametro quedo en una diferente posicion de la tabla y esta se ajusto automaticamente, como venimos repitiendo hasta ahora esta conducta corresponde unicamente si es el ultimo argumento o el unico, veamos como es en otro caso:

t = { foo0(), foo2(), 4 }	<==>	t[1] = nil, t[2] = "a", t[3] = 4

En este caso vuelve a tomar la accion de los casos anteriores, es decir solamente tomara el primer valor de cada funcion, observen como en el primer caso esta vez si tomara nil porque estamos asignandolo a una variable, en el segundo caso tomara el primer valor y en el tercer caso tomara el valor informado, veamos el siguiente ejemplo:

foo.lua

function foo0() end
function foo1() return "a" end
function foo2() return "a", "b" end

function foo(i)
        if i == 0 then return foo0()
        elseif i == 1 then return foo1()
        elseif i == 2 then return foo2()
        else print("Se excedio de numeros")
        end
end

print(foo(1))
print(foo(2))
print(foo(0))
print(foo(3))
Anuncios

Primero crearemos nuestras tres funciones: foo0, foo1 y foo2 con las mismas respuestas que vimos hasta ahora, luego crearemos una funcion llamada foo con un argumento, dentro del bloque crearemos un condicional que chequeara el valor del argumento informado en i, si se cumple la primer condicion retornara a la funcion foo, sino pasara a la segunda condicion donde en caso de ser cierta devolvera a la funcion foo1, seguiremos con la otra condicion en caso de fallar las anteriores y si es verdad devolvera a foo2, por ultimo en caso de fallar todas las anteriores nos mostrara un mensaje de error gracias a else, para finalizar tendremos cuatro print donde pasaremos a foo como argumento y a su vez en foo enviaremos un numero como argumento, probemos nuestro programa y veamos su salida:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 foo.lua
a
a       b

Se excedio de numeros

tinchicus@dbn001dsk:~/lenguaje/lua$

Vemos que el primer print nos devolvio a foo1, en el segundo nos devolvio a foo2, en el tercer caso nos devolvio nada, foo0, y para el ultimo print nos devolvio el mensaje de error, pero nosotros tambien podemos forzar a una funcion a devolver un solo valor, en caso de ser necesario, mediante el encierro por partentesis, veamos unos ejemplos:

print((foo0()))		<==>	nil
print((foo1()))		<==>	a
print((foo2()))		<==>	a

En el primer caso como no tiene devolucion va a ser nil, en el segundo caso como solo devuelve el valor “a” pero en el tercer caso solo nos devolvera el valor “a” e ignorara a la valor “b”, una cosa a tener en cuenta es que las declaraciones de devolucion no necesitan parentesis a su alrededor del valor retornado, cualquier par de parentesis ubicado aqui cuenta como un par extra, por lo tanto una declaracion como: return (f(x)) siempre devolvera un solo valor y no importa cuantos valores devuelva f.

Una funcion especial para los multiples resultados es table.unpack, este recibe un array y devuelve como resultado todos los elementos del mismo, y recuerden que siempre comenzara desde el indice 1, veamos el siguiente ejemplo:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> print(table.unpack{ 10,20,30 })
10      20      30
> a, b = table.unpack{ 10,20,30 }
> print(a, b)
10      20
>
Anuncios

En este caso vemos dos casos, en el primero imprime directamente valor por valor de los informados en table.unpack, en cambio en el segundo caso lo usamos para una asignacion multiple, y como estuvimos viendo hasta ahora al ser dos variables (a, b) y tres valores (10, 20, 30), al asignarlo el tercer valor fue descartado, otro uso muy comun de table.unpack es en el mecanismo de llamada generica, este mecanismo nos permite llamar a cualquier funcion con cualquier cantidad de argumentos de forma dinamica, a diferencia de C que no podemos hacer este tipo de llamadas porque las funciones tienen un numero fijo de argumentos, en cambio en Lua si queres llamar a una funcion llamada f con argumentos variables en una array llamado a podemos hacerlo simplemente con:

f(table.unpack(a))

Este tipo de llamada hace que table.unpack devuelva todos los valores en a y los transforme en argumentos, veamos el siguiente ejemplo:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> f = string.find
> a = { "Hola Mundo", "la" }
> f(table.unpack(a))
3       4
>

Tenemos a f que contiene una funcion sin sus parentesis, luego en a tendremos el array que pasaremos, en la siguiente linea tenemos el caso visto anteriormente donde esa linea equivaldria a esto:

string.find(table.unpack({ "Hola Mundo", "la" })
Anuncios

Porque son los valores contenidos en las variables, por eso al mostrarlo en pantalla nos devuelve los valores de indice del primer y ultimo caracter, aunque table.unpack esta predefinida y escrita en C nosotros podriamos escribir un codigo similar en Lua usando recursion, veamos el siguiente ejemplo:

tabla = { 10, 20, 30, 5, 11, 100 }

function unpack(t, i)
        i = i or 1
        if t[i] then
                return t[i], unpack(t, i+1)
        end
end

print(unpack(tabla))

Para este caso primero creamos una tabla llamada tabla con 6 valores, luego crearemos una funcion llamada unpack la cual recibira dos argumentos, la primera es el valor (t) y la segunda es el indice (i), la primera vez como informamos un solo argumento i recibira por medio de or el valor 1 luego al existir un valor en i utilizara ese para el programa, luego tendremos un condicional donde evaluara si t[i] es distinto de nil, es decir posee un valor, si se cumple esa condicion nos devolvera el valor de la posicion actual pero luego se llamara a si mismo para informar nuevamente el valor de t y despues el valor actual de i mas 1, es decir en la primera vuelta informara 2, luego sera 3 y asi hasta que exista un valor nil con el cual omitira el condicional, no se volvera a llamar a si mismo y saldra de la funcion, nuestra ultima linea muestra el resultado de unpack con respecto a tabla, probemos el ejemplo para ver su salida:

tinchicus@dbn001dsk:~/lenguaje/lua$ lua5.3 unpack.lua
 10      20      30      5       11      100
 tinchicus@dbn001dsk:~/lenguaje/lua$

Vean como el programa desgloso perfectamente la tabla e hice exactamente lo mismo que si hubieramos utilizado table.unpack.

Anuncios

En resumen, hoy hemos visto multiples resultados de una funcion, hemos visto como utilizarlo, hemos visto sus distintas conductas, como se comporta con respecto a otras reglas de Lua, hemos aplicado algunos ejemplos, hemos visto a table.unpack, y hemos hecho una equivalencia, 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