Funciones
Todos hemos usado funciones de una u otra forma en muchos lenguajes de programación. En Lua también tenemos funciones y la idea es muy similar a la de los demás lenguajes. Sin embargo, hay algunos matices que es interesante considerar.En Lua una función se puede definir de varias formas. La más sencilla y quizás la más utilizada es:
function suma(a, b)
return a + b
end
De esta forma, estamos definiendo una función denominada "suma" de ámbito global (o sea, accesible desde cualquier sitio), que acepta dos argumentos, a y b, suma los dos argumentos y retorna el resultado de la suma. Creo que todo esto está bastante claro para todos aquellos que alguna vez han programado en algún lenguaje, no parece haber nada nuevo. Pero quiero destacar el hecho de que la función tiene ámbito global, es decir, que podemos invocarla desde cualquier punto de nuestro programa. Esto ocurre porque no hemos utilizado la palabra clave "local". Si hubiéramos querido definir una función con ámbito local:
local function suma(a, b)
return a + b
end
Otro tema interesante a destacar es que los parámetros, a y b, no declaran un tipo. Es decir, podemos invocar a la función "suma" con dos valores o varialbes cualquiera, de cualquier tipo. Lo normal será invocarla con dos números, pero nada nos impide utilizar otros tipos (eso sí, probablemente tendremos un error de ejecución, por ejemplo al invocar la función "suma" con dos strings).
Destacar también que la función no declara el tipo retornado. Retornará un valor numérico porque la sentencia "return" devuelve la suma de sus dos parámetros, pero realmente el lenguaje no obliga a retornar ningún tipo concreto. De hecho (reconozco que esto es algo enrevesado), la función podría retornar en un caso un número y en otros casos un string.
Por último no quiero olvidar que una función podría no retornar ningún valor. Para ello podemos utilizar la palabra clave "return" simplemente o bien llegar al final de la función sin retornar ningún valor.
Funciones dentro de una tabla
Como vimos al hablar de las tablas, una función puede ser un miembro de una tabla:local table = {
add = suma,
}
O bien:
local table = {
add = function(a, b) return a + b end,
}
Retorno de múltiples resultados
Una función puede retornar más de un resultado:function statistics()
return a, b, c
end
Estos múltiples resultados se recogen de la siguiente forma:
local x, y, z = statistics()
Número variable de argumentos
Una función no sólo puede retornar múltiples resultados, si no que también puede aceptar un número variable de argumentos:
function guarda( ... )
print( arg[1], arg[2], arg[3] )
end
Los argumentos se recogen en la lista predefinida arg. El operador # nos permite conocer el número de argumentos que se han pasado a la función:
function guarda( ... )
for i = 1, #arg do -- más adelante veremos los bucles
print( arg[i] )
end
end
También podemos tener un número mínimo de argumentos a una función y los demás variables:
function guarda( a, b, ... )
print( a, b, arg[1], arg[2], arg[3] )
end
Lo importante es que los tres puntos (argumentos variables) siempre tienen que ir al final.
Más sobre tablas y funciones
Ya he dicho varias veces que una función puede ser un miembro de una tabla. Esto es muy importante y se utiliza mucho en Lua. Una de las ventajas de esta construcción es que de esta forma no se crean funciones globales. Podríamos resolver también este problema con la palabra "local", pero en este caso el acceso a la función sería únicamente local, sin embargo, las funciones dentro de una tabla son accesibles desde cualquier punto del programa, pero de una forma que podría ser similar a los namespaces o packages de otros lenguajes (como C# o Java). Por ejemplo:
myobject = {
add = function(a, b) return a + b end,
}
add = function(a, b) return a + b end,
}
Para invocar a esta función haríamos:
myobject.add(3, 5)
De esta forma estamos añadiendo un "namespace" a la función. Y no sobreescribiría a esta otra función que tiene el mismo nombre, pero está en otro "namespace":
utils = {
add = function(a, b) return a .. b end,
}
add = function(a, b) return a .. b end,
}
myobject = { }
function myobject.add(a, b) return a + b end
function myobject.add(a, b) return a + b end
Tablas, funciones y objetos
Lo que hemos visto sobre tablas y funciones hará pensar a algún lector en objetos y en programación orientada a objetos. Sin embargo, la forma que hemos visto de meter una función en una tabla no es realmente un objeto. El motivo principal es que la función no es "consciente" de en qué objeto (tabla) está, de hecho, no tiene acceso a otros datos de la tabla. Veámoslo con un ejemplo:
myobject = {
dato = 3,
}
function myobject.calcula(a) return dato + a end
dato = 3,
}
function myobject.calcula(a) return dato + a end
En este caso tenemos una tabla con dos miembros: un número "dato" y una función "calcula".
Si intentamos invocar a la función "calcula":
myobject.calcula(5)
Obtendremos un error similar al siguiente:
attempt to perform arithmetic on global 'dato' (a nil value)
Esto se debe a que no existe una variable global llamada "dato" y por tanto su valor es nil, lo cual provoca un error al intentar sumarlo con el argumento "a". La variable "dato" de la tabla no es accesible a la función.
Podríamos resolverlo de la siguiente forma:
myobject = {
dato = 3,
}
function myobject.calcula(a) return myobject.dato + a end
Pero tenemos que reconocer que no es una forma muy elegante de acceder a la variable dato (ya que dependemos del nombre que demos a la tabla, en este caso, "myobject").dato = 3,
}
function myobject.calcula(a) return myobject.dato + a end
Otra solución, tampoco muy elegante por cierto, sería pasar como argumento a la función la propia tabla (reconozco que esto es todavía peor):
myobject = {
dato = 3,
}
function myobject.calcula(this, a) return this.dato + a end
myobject.calcula(myobject, 5)
dato = 3,
}
function myobject.calcula(this, a) return this.dato + a end
myobject.calcula(myobject, 5)
myobject = {
dato = 3,
}
function myobject:calcula(a) return self.dato + a end
Y para invocar a esta función:
myobject:calcula(6)
Es una sintaxis un poco extraña, pero fácil de comprender. En primer lugar, para añadir la función a la tabla utilizamos dos puntos, en lugar de un punto como hemos hecho hasta ahora. También utilizamos dos puntos para invocar a la función. Los dos puntos indican a Lua que tenemos que necesitamos un parámetro intrínseco, denominado "self", y que es la propia tabla donde está la función. Este parámetro lo pone Lua (sin que hagamos nada y de forma similar al parámetro arg que Lua introduce en las funciones con número variable de argumentos).
En una entrada posterior trataré el tema de la programación orientada a objetos en Lua, donde veremos cómo podemos tener objetos reales en nuestros programas, a pesar de que Lua no es realmente un lenguaje orientado a objetos. Tendremos que hacer unas construcciones especiales, siguiendo un determinado patrón para conseguirlo.
dato = 3,
}
function myobject:calcula(a) return self.dato + a end
Y para invocar a esta función:
myobject:calcula(6)
Es una sintaxis un poco extraña, pero fácil de comprender. En primer lugar, para añadir la función a la tabla utilizamos dos puntos, en lugar de un punto como hemos hecho hasta ahora. También utilizamos dos puntos para invocar a la función. Los dos puntos indican a Lua que tenemos que necesitamos un parámetro intrínseco, denominado "self", y que es la propia tabla donde está la función. Este parámetro lo pone Lua (sin que hagamos nada y de forma similar al parámetro arg que Lua introduce en las funciones con número variable de argumentos).
En una entrada posterior trataré el tema de la programación orientada a objetos en Lua, donde veremos cómo podemos tener objetos reales en nuestros programas, a pesar de que Lua no es realmente un lenguaje orientado a objetos. Tendremos que hacer unas construcciones especiales, siguiendo un determinado patrón para conseguirlo.
Closure de una función
El closure de una función es un término que suele ser difícil de comprender de partida, salvo que se haya utilizado previamente en otros lenguajes como javascript.
En pocas palabras, el closure de una función son las variables y funciones que existen en su scope. Cuando una función está dentro de otra función, la función interna tiene acceso a todas las variables de la función externa. A esto se le conoce como lexical scope.
Estos dos conceptos que parecen a primera vista muy sencillos, se complican un poco, como ahora veremos.
local f = external(5)
print( f(4) )
Este es un ejemplo bastante complejo, si no se tiene costumbre de manejar funciones como objetos. Voy a tratar de explicarlo despacio.
En primer lugar, tenemos una función llamada external que devuelve otra función llamada internal. Hasta aquí no hay nada extraño. Sin embargo, la función internal utiliza variables e incluso parámetros (varExt y paramExt) que pertenecen a la función external. Y nos podemos preguntar: ¿Cuánto valen esa variable y ese parámetro cuando mucho más abajo llamamos a la función f? Pues bien, esa variable y ese parámetro tienen los valores que tenían cuando llamamos a la función external. Es decir, paramExt vale 5 y varExt vale 6. Por tanto, la función f, que devuelve la suma de esos dos objetos más el parámetro paramInt, devuelve 5 + 6 + 4, o sea, 15.
La variable varExt y el parámetro paramExt son el closure de la función internal, y cuando se le invoque más adelante, tendrá acceso a esos valores de su closure. Cada vez que invocamos a la función external y recibimos una función como resultado, esa función tendrá asociado el closure correspondiente.
No hay comentarios:
Publicar un comentario