Vistas de página en total

Mostrando entradas con la etiqueta lua. Mostrar todas las entradas
Mostrando entradas con la etiqueta lua. Mostrar todas las entradas

jueves, 8 de septiembre de 2016

Programación orientada a objetos en Lua

Lua, al igual que otros lenguajes no está orientado a objetos. Otros lenguajes como Java o C# tienen palabras y construcciones especiales para definir clases, crear objetos, herencia, etc. Pero Lua no.
Los conocedores de javascript con un nivel elevado saben que javascript tampoco está orientado a objetos, pero que se pueden realizar algunas "artimañas" para conseguir algo similar a la orientación a objetos. En Lua ocurre exactamente los mismo: utilizando las funciones, y las tablas podemos conseguir una funcionalidad de orientación a objetos muy similar a la de otros lenguajes.
Es importante notar que no existe una única forma de conseguir OOP en Lua, sino que cada desarrollador suele tener una arquitectura, al igual que ocurre en javascript. Yo mostraré la que yo suelo utilizar, que no tiene porqué ser la mejor forma, pero que es muy sencilla de entender y a mí me ha servido durante muchos años.

Clase base

En primer lugar, definiremos una clase base en el fichero BaseClass.lua:

function BaseClass( param1, param2 )
    local this = {}
    local privateAttr = param1
    this.publicAttr = param2
    local function privateMethod()
        return privateAttr
    end
    this.publicMethod = function()
        return privateMethod()
    end
    return this
end

En este ejemplo, hemos definido una función constructora, BaseClass(), de una clase que tiene dos atributos: privateAttr (que es privado) y publicAttr (que es público). También se han definido dos métodos: privateMethod (que es privado) y publicMethod (que es público).
En primer lugar, se crea una tabla (llamada this, aunque valdría cualquier otro nombre). Luego se crean atributos en la tabla this (que serán los atributos públicos) y también funciones en la tabla (que serán las funciones públicas). Los atributos y funciones que sean local se convertirán en atributos y funciones privados.
Por último retornamos la tabla this que será el objeto que acabamos de crear con los atributos y métodos públicos.
En este momento, lo normal es hacerse la pregunta: ¿Qué ocurre con las variables y funciones local? ¿Desaparecen al retornar? Pues no, esas variables y funciones siguen existiendo en el closure de las funciones que hemos metido dentro de la tabla this, y por tanto, las funciones públicas podrán seguir accediendo a ellas una vez retornado this.

Clase derivada

Ahora vamos a definir una clase que hereda de la clase base, en el fichero DerivedClass.lua :

require ("BaseClass")

function DerivedClass( param1, param2, param3 )
    local this = ClaseBase(param1, param2)
    this.attr1 = param1
    this.attr2 = param2
    local attr3 = param3
    local function pp()
        print( this.attr1, attr3, this.publicMethod() ) 
    end
    this.increment = function()
        this.attr1 = this.attr1 + 1
        this.attr2 = this.attr2 + 1
    end
    return this
end

En primer lugar tenemos que hacer require del módulo BaseClass para tener acceso a la función constructora de la clase base.
En este módulo definimos otra función constructora, DerivedClass(), similar a la de la clase base. Pero hay algo distinto. En la clase base creábamos una tabla this vacía, y ahora estamos llamando a la función constructora de la clase base para que nos retorne this. A partir de ese momento, vamos añadiendo nuevos atributos y métodos a this, tal y como hicimos antes.
Terminamos, al igual que antes, retornando la tabla this con los atributos de la clase base y los de la clase derivada.

Utilización

Por último, vamos a utilizar la clase DerivedClass en un fichero main.lua:

require ( "DerivedClass")

local obj = DerivedClass( 1, 2, 3 )
obj.increment()
print(obj.publicAttr)
print(obj.attr1, obj.attr2)

local obj2 = DerivedClass( 4, 5, 6 )
obj2.increment()
print(obj2.publicAttr)
print(obj2.attr1, obj2.attr2)

No es difícil entender este main.lua. Lo primero que tenemos que hacer es la sentencia require para llegar a la clase derivada (no hace falta el require de la clase base, porque eso ya está dentro del módulo de la clase derivada).
Luego creamos objetos de la clase derivada, llamando a la función constructora. Y por último accedemos a sus atributos y métodos públicos.

Otras opciones para OOP en Lua

Como ya he dicho, esta es la forma en que yo hago orientación a objetos en Lua, pero existen muchas otras alternativas. Debido a que Lua no es orientado a objetos, entonces tenemos que "simular" esta característica con construcciones propias del lenguaje. Esta que he descrito es mi forma de hacerlo y hasta ahora me ha funcionado muy bien. De OOP lo que más necesito es la encapsulación, métodos públicos y privados, y la herencia. Con mi método consigo todo lo que necesito de una forma sencilla y que funciona.
Sin embargo, como también he dicho existen otras alternativas. Quizás una de las más conocidas sea la que propone la web de Lua:


También en Corona labs tienen una propuesta:


Y por último, en la wiki de Lua hay otro tutorial también interesante:


Con alguna de estas opciones tendremos características más avanzadas de la orientación a objetos en Lua, pero ya digo que en mi opinión no las he necesitado nunca, y el problema es que utilizan características avanzadas de Lua que pueden complicar el software que desarrollamos.










martes, 6 de septiembre de 2016

Módulos

Cuando escribimos una aplicación con Corona SDK empezamos con un fichero llamado main.lua
Este fichero es el punto de entrada de nuestra aplicación y siempre será el punto de entrada por el que empieza la ejecución. Si quisiéramos podríamos escribir todo nuestro programa en un único fichero main.lua, lo cual es perfectamente posible, salvo que sería bastante difícil su mantenimiento si la aplicación tiene un tamaño moderado o grande. Otra limitación sería el propio editor o IDE, que probablemente tendría algunos problemas de rendimiento si main.lua tuviera muchas miles de líneas de código Lua. Desde luego, desde un punto de vista de ingeniería del software y de arquitectura, ésta no es la mejor idea (excepto para aplicaciones muy triviales o pruebas).

Los módulos Lua permiten que organicemos mejor nuestro programa. Un módulo es un fichero de código Lua que se incluye en main.lua mediante la sentencia require()
Por ejemplo, si tenemos un módulo en un fichero llamado utils.lua, en el main.lua tendremos que hacer:

    require( "utils" )

Obsérvese que no hay que añadir la extensión del fichero .lua

El contenido del fichero utils.lua podría ser:

    function add(a, b)
        return a + b
    end

Entonces, en main.lua, después de require(), podemos invocar a la función add().

En este ejemplo estamos declarando una función en el contexto global, lo cual no es una buena idea. Por eso, lo normal es que un módulo retorne una tabla con los datos y funciones. Por ejemplo:

    local t = {}

    t.add = function(a, b)
        return a + b
    end

    return t

De esta forma, en el main.lua tendremos que hacer lo siguiente:

    local utils = require( "utils" )
    print( utils.add( 5, 34 )

La sentencia require() devuelve el valor retornado por el fichero del módulo. En nuestro ejemplo, retorna una tabla que tiene una función.

Cualquier variable o función declarada en un modulo con local, queda en el ámbito local del módulo y no se puede acceder a ella desde otros sitios fuera del módulo.

Otro punto importante es que si ejecutamos la sentencia require() varias veces en nuestro programa, sólo se ejecutará el módulo la primera vez. Las siguientes veces simplemente detectan que ya se ha hecho require() de ese módulo y no lo vuelve a ejecutar, aunque sí que retorna el mismo valor que devolvió la primera vez.

Se podrían ejecutar todas las sentencias require() de todos los módulos en main.lua y asignar el resultado a variables globales, por ejemplo:

    utils = require( "utils" )
    constants = require( "constants" )

Las variables utils y constants son globales y por tanto podremos acceder a ellas desde cualquier punto de nuestro programa. Esta es una práctica razonable, pero se recomienda que cada módulo haga los require() que necesite y que asigne el resultado a variables locales:

    local utils = require( "utils" )
    local constants = require( "constants" )

En este caso, usaríamos en cada módulo otros módulos que se necesiten, y como no se ejecuta el módulo nada más que la primera vez, no hay problemas de rendimiento.

Por último, y para terminar el tema de los módulos Lua, quiero comentar que los ficheros de los módulos se pueden colocar en distintos subdirectorios (siempre debajo del directorio del proyecto donde debe estar obligatoriamente main.lua). Podríamos tener una estructura similar a la siguiente:


  • main.lua
    • utils
      • dateutils.lua
      • file.lua
    • scenes
      • splash_scene.lua
      • menu_scene.lua

Para acceder a estos módulos, se haría de la siguiente forma:

    require( "utils.dateutils" )

O sea, los subdirectorios se especifican con un punto y no hay que poner la extensión .lua






jueves, 1 de septiembre de 2016

Lua - Parte 5

Estructuras de control

Lua, como casi todos los lenguajes de programación, tiene una serie de estructuras de control que permiten iterar y hacer comprobaciones dentro del código:
  • if
  • for
  • while
  • repeat

IF

La sentencia if permite chequear una o más condiciones:

    if condición1 then
        sentencias1
    elseif condición2 then
        sentencias2
    elseif condición3 then
        sentencias3
    else
        sentencias4
    end

Las condiciones son expresiones cuyo resultado es un boolean, o sea, true o false. Si una expresión es nil se considera false, cualquier otro valor, se considera true. Ejemplos de condiciones:

    3 > 5    -- false
    5 > 1    -- true
    a        -- variable no existente -> nil -> false
    6        -- true
    0        -- true
    "hola"   -- true
    ""       -- true
    nil      -- false

Las partes elseif y else de la sentencia if son opcionales.

Lua no tiene la sentencia switch típica de otros lenguajes de programación, pero se puede sustituir muy fácilmente con la sentencia if.

FOR

Forma general de la sentencia for:

    for variable = start, end, step do
        sentencias
    end

La "variable" es el índice que va cambiando y que se puede utilizar dentro de las sentencias del bucle for. "start" es el valor de inicio y "end" es el valor final (incluidos ambos). "step" es opcional y permite pasar desde start hasta en end en pasos distintos de uno.

Ejemplo:

    for i = 3, 10 do
        print(i)
    end

Otro ejemplo, contando hacia atrás:

    for i = 100, 50, -1 do
        print(i)
    end

WHILE

La sentencia while permite ejecutar un bloque de sentencias mientras se cumpla una condición:

    while condición do
        sentencias
    end

REPEAT

La sentencia repeat permite ejecutar un bloque de sentencias mientras hasta que se cumpla una condición:

    repeat
        sentencias
    until condición

El bloque repeat se ejecuta siempre al menos una vez.

BREAK

La sentencia break permite salirse un bucle. Por ejemplo:

    for i = 100, 50, -1 do
        print(i)
        if i > 80 then
            break
        end
    end

Hay que tener en cuenta que Lua no tiene la sentencia continue, típica en otros lenguajes de programación para ir al principio del bucle.

Lua - Parte 4

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

En este caso, sólo podríamos invocarla desde el módulo en el que estamos (ya hablaremos de módulos en una entrada posterior, de momento adelanto que un módulo es un fichero Lua con funciones y otros elementos de código).

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,
    }

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,
    }

Otra forma equivalente de añadir la función a la tabla sería:

    myobject = { }
    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

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").

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)

Aunque esta solución es incluso más horrible que la anterior, me permite introducir la solución que propone Lua para estos casos.

    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.

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 function external(paramExt)        local varExt = paramExt + 1        local internal = function(paramInt)            local varInt = paramInt + varExt + paramExt        end        return internal    end

    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.



lunes, 29 de agosto de 2016

Lua - Parte 3

Tablas

Las tablas son la estructura de datos más potente que tiene Lua. Pero al mismo tiempo es quizás una de las más difíciles de entender (los programadores javascript tienen ventaja en este caso, y entenderán las tablas Lua muy rápidamente).

¿Qué son tablas Lua?

Muy brevemente: las tablas Lua son objetos. O mejor dicho, son arrays asociativos, o sea, arrays donde se puede acceder a los elementos por un identificador (el cual, como veremos, puede ser numérico o no númerico). Esta primera explicación de las tablas es perfecta para programadores javascript, los cuales ya habrán entendido un porcentaje muy alto de lo que son las tablas Lua.
Para programadores que vengan de otros lenguajes, las tablas son muy parecidas a los diccionarios o mapas.
Como vamos a ver, con las tablas Lua podemos implementar arrays (indexados por índice numérico), mapas (accedidos con identificador no númerico) e incluso objetos.

Arrays

Los arrays en Lua son tablas:

    local array = { "a", "b", "c" }

Se puede acceder a los elementos con la notación típica de corchetes:

    array[2] = "b"
    var v = array[3]

Es importante indicar que los arrays en Lua empiezan con el índice 1 (no 0 como ocurre en muchos otros lenguajes de programación). Así, en la tabla anterior, el elemento 1 vale "a", el elemento 2 vale "b" y el elemento 3 vale "c".
En un array podemos guardar elementos de distintos tipos:

    local otroArray = { 1, "a", 45, "Toni" }

Se puede obtener la longitud de un array con el operador # como anticipé en la entrada anterior:

    local len = #otroArray

Por último, podemos crear un array vacío y añadirle luego los elementos:

    local array = { }
    array[1] = "a"
    array[2] = "b"
    array[3] = "c"

Podríamos incluso dejar "huecos" y entonces los elementos sin rellenar sería nil:

    array[5] = "e"
    print(array[4])     -- nil

Mapas o diccionarios

Como he dicho, con las tablas Lua también podemos implementar mapas o diccionarios:

    local socio = { nombre = "Luis", edad = 23, dni = "34756453H" }

En este caso, hemos dado un nombre a cada elemento de la tabla. Este nombre se conoce como identificador y nos permite acceder a los distintos elementos:

    socio["nombre"] = "Luis"
    var age = socio["edad"]

Como se ve, para acceder a los elementos tenemos que entrecomillar el identificador. Si no ponemos las comillas, Lua pensaría que edad es una variable. Por ejemplo:

    print(socio[edad])

La variable edad no existe, y por tanto, el resultado en la consola sería nil.
Lua también permite otra notación para acceder a los elementos por su identificador:

    print(socio.edad);
    socio.nombre = "Ramón"

De esta forma, no se ponen comillas al identificador.
Cuando queremos acceder a un elemento de un mapa de forma indirecta, entonces tendremos que utilizar la notación de corchetes de forma obligatoria:

    var element = "edad"
    print(socio[element])

En este caso, se accede al elemento socio["edad"] o bien socio.edad de forma indirecta.

Al igual que ocurre con los arrays, podemos crear un mapa vacío y añadirle elementos después:

    var socio = { }
    socio.nombre = "Luis"
    socio["edad"] = 34

Incluso, un caso más enrevesado, podemos tener una estructura de datos que sea un mapa y que tenga elementos de array (yo no recomiendo esta práctica, aunque Lua lo permite):

    var socio = { }
    socio.nombre = "Luis"
    socio[3] = "XXX"

pero en este caso, sólo podremos acceder al elemento 3 con la notación de corchetes:

    print(socio[3])      -- correcto
    print(socio.3)        -- error

El operador # para obtener la longitud de un array sólo funciona para arrays "puros", es decir, no funcionará en el caso de mapas.

Anidación

Los elementos de un tabla (o de un array) pueden ser de cualquier tipo, y por tanto, un elemento podría ser un array o tabla, permitiendo así estructuras anidadas:

    local t = { }
    t.nombre = "Luis"
    t.direccion = { }
    t.direccion.calle = "Gran Vía"
    t.direccion.poblacion = "Madrid"

Funciones como elementos de una tabla 

Aunque todavía no hemos visto en profundidad las funciones y sus características, es importante reseñar que una función puede ser un elemento de una tabla. Veamos varios ejemplos:

    local t =  { }

    t.calculo = function(a, b)  return a+b end

    function suma(a,b)
        return a + b
    end
    t.calculo = suma

En el primer caso asignamos una función inline a un elemento de una tabla, y en el segundo caso, asignamos una función existente a un elemento de una tabla.






















miércoles, 24 de agosto de 2016

Lua - Parte 2

Variables, valores y tipos

Lua es un lenguaje tipado dinámicamente. Para los que saben javascript, esto es sencillo de entender. ¿Qué significa que un lenguaje es tipado dinámicamente? Es muy sencillo. En lenguajes como Java, cuando se declara una variable hay que declarar su tipo, int, float, String o lo que sea, y esa variable sólo podrá almacenar valores del tipo declarado (o bien de una clase derivada).
En lenguajes tipados dinámicamente, como Lua, una variable puede contener un valor de cualquier tipo, o dicho más exactamente, puede apuntar a valores de cualquier tipo. Además, es posible que una variable que apunte a un valor de un tipo, más tarde apunte a un valor de otro tipo.
Por ejemplo:

    contador = 14

Estamos declarando una variable que se llama contador y que apunta a un valor de tipo numérico que vale 14. Se observa que no hemos declarado ningún tipo para la variable.
Más adelante podríamos hacer:

    contador = "43"

Ahora, la misma variable contador apunta a otro valor, esta vez de tipo string.

Tipos de datos en Lua:

  • nil - indica que la variable no ha sido inicializada (también se puede asignar nil a una variable explícitamente).
  • boolean - puede ser true o false.
  • number - cualquier número, entero o real. Lua utiliza punto flotante de doble precisión para todos los números.
  • string - cadena de caracteres.
  • function - un bloque de código que se puede invocar.
  • table - es el tipo más complejo que tiene Lua. Más adelante veremos espcíficamente las tablas.
Una variable se puede declarar en cualquier lugar simplemente asignándole un valor:

    contador = 14

Cuando se declara una variable de esta forma, tiene un ámbito global y existirá durante toda la ejecución del programa. Podremos acceder a esta variable desde cualquier punto del programa. Si queremos que el recolector de basura elimine la memoria ocupada por la variable, tenemos que asignarle nil:

    contador = nil

Se dice que el ámbito o scope de esta variable es global. Pero también existe un ámbito de bloque, por ejemplo dentro de una función:

    function calculate()
        local total = 7
    end

Al poner local, la variable total tiene ámbito de bloque: sólo existe dentro de la función, y desaparecerá cuando retorne la función. El recolector de basura eliminará la memoria ocupada por la variable cuando retorne la función (incluso aunque no le asignemos nil).
Se recomienda siempre utilizar local para las variables, excepto cuando deseemos que una variable tenga ámbito global. Cuando dentro del programa se utiliza una variable (sin usar local), Lua busca en el ámbito local para ver si existe, si no existe, busca en el bloque superior y así sucesivamente hasta llegar al ámbito global. Si no existiera, la variable se crea. El rendimiento será muy superior si declaramos todas las variables local, para evitar la cadena de búsquedas de Lua. Además, todos sabemos que las variables globales no son buenas, porque es muy fácil cometer un error y sobreescribirlas en algún lugar del programa sin darnos cuenta.
También hay que tener en cuenta que cuando declaramos una variable local en un bloque, esta variable se utiliza en el bloque aunque exista otra con el mismo nombre en un bloque externo o bien en el ámbito global. Por ejemplo:

    contador = 3
    function incrementar()
        local contador = contador + 1
    end
    -- aquí el valor de contador es 3

Hay una forma de acceder directamente a una variable global, aunque tengamos variables locales con el mismo nombre y además, evitando la cadena de búsqueda de Lua:

    contador = 3
    function incrementar()
        local contador = contador + 1
        _G.contador = _G.contador + 1
    end
    -- aquí el valor de contador es 4

Lua almacena todas las variables globales en una tabla llamada _G, por tanto podemos acceder a cualquier variable global a través de esta table.

Las variables locales de bloque no sólo se pueden utilizar dentro de una función, también se pueden utilizar en otros bloques como if o bucles.

En Lua se pueden realizar asignaciones múltiples:

    local x, y = 3, 4

Esto permite realizar hacer un swap de dos variables de una forma muy sencilla:

    x, y = y, x

Expresiones y operadores

Los operadores y expresiones en Lua son muy similares a los de otros lenguajes de programación.
Operadores matemáticos:
  • + suma
  • - resta o negativo
  • * multiplicación
  • / división
  • % módulo
  • ^ exponenciación
Operadores de comparación (retornan true o false):
  • == igualdad (si los tipos no son iguales, retorna false)
  • ~= desigualdad
  • < menor que
  • > mayor que
  • <= menor o igual que
  • >= mayor o igual que
Operadores lógicos:
  • and
  • or
  • not
Los operadores lógicos consideran que false y nil son false, y todos los demás valores son true.
Los operadores and y or hacen una evaluación con corto-circuito, es decir, si el primer valor de un and es false, no evalúa el segundo, y si el primer valor de un or es true, no evalúa el segundo.

Operador de concatenación:
  • .. concatenación de strings y números
Operador longitud:
  • # longitud de un array (veremos este operador cuando estudiemos las tablas, porque un array es una tabla en Lua)











martes, 23 de agosto de 2016

Lua - Parte 1

El lenguaje Lua

Corona SDK proporciona los APIs necesarios para desarrollar aplicaciones multiplataforma para dispositivos móviles. El lenguaje de programación para utilizar esos APIs es Lua. Por eso, necesitamos un conocimiento lo más profundo posible del lenguaje Lua si queremos desarrollar con soltura aplicaciones. Poer no hay que asustarse, en primer lugar, la sintaxis de Lua, además de ser muy similar a la de otros lenguajes (Javascript por ejemplo), es muy sencilla.

Según la web de Lua: Lua es un lenguaje de programación potente, eficiente y ligero, es un lenguaje de script embebido. Soporta programación procedural, programación orientada a objetos (aunque no de forma nativa como Java o C#), programación funcional y descripción de datos.

Lo más relevante es que Lua no es un lenguaje para escribir programas stand-alone, como lo son Java, C++, C# etc. Un programa Lua se tiene que integrar (embeber) en un programa contenedor (host). El programa host invoca al programa Lua y puede acceder a sus datos. También es posible invocar a rutinas del programa host desde Lua. En realidad, todo esto es cultura sobre Lua, porque en la práctica nosotros vamos a desarrollar 100% con Lua, y es Corona SDK quien se encarga de realizar el trabajo sucio de integrar nuestro programa Lua con los APIs para aplicaciones móviles. Corona es el programa host. Nosotros usaremos el lenguaje Lua para implementar la lógica e invocaremos los APIs de Corona para crear nuestras aplicaciones. Nada más.

Otra característica importante de Lua es que tiene un recolector de basura integrado, y por tanto, no es necesario liberar la memoria que vamos creando, se libera automáticamente cuando ya no se usa. Aunque es recomendable desreferenciar las variables que ya no se van a usar (ya veremos esto más adelante) para facilitar la tarea al recolector de basura.

Sintaxis de Lua

Lua es uno de los lenguajes de programación inspirado "más o menos" en C (como otros muchos, Java, C++, C#, Javascript, etc). Esto significa, en primer lugar, que Lua es sensible a mayúsculas y minúsculas.
Las sentencias pueden terminar opcionalmente en punto y coma.
Los nombres de variables, funciones o cualquier cosa que pueda tener nombre, son una combinación de letras, dígitos y subrayados, de cualquier longitud, Pero no pueden empezar con un dígito. Se deben evitar nombres que empiecen con subrayado, porque aunque son legales en Lua, sin embargo, por convención, Lua utiliza nombres que comienzan por subrayado para sus variables internas.
Los bloques son una serie de sentencias que se ejecutan una detrás de otra y comparten un contexto de ejecución. El bloque más común es la función:

function calculate()
    -- bloque de sentencias
end

Todas las sentencias dentro de la función son un bloque.
Otros bloques Lua son las sentencias if y los bucles.
También es posible definir bloques anónimos:

do
    -- sentencias
end

Palabras clave

Esta es la (breve) lista de palabras clave del lenguaje Lua:
  • and
  • break
  • do
  • else
  • elseif
  • end
  • false
  • for
  • function
  • goto
  • if
  • in
  • local
  • nil
  • not
  • or
  • repeat
  • return
  • then
  • true
  • until
  • while

Operadores

Los operadores en Lua son similares a los de la mayoría de los lenguajes de programación. Sin embargo, hay algunos operadores que son distintos y merece la pena mencionarlos:

  • ∼= operador distinto
  • .. concatenación de strings
  • . . . número variable de argumentos en una función

Comentarios

En Lua, como en muchos lenguajes de programación, existen dos tipos de comentarios.
Los comentarios de línea:

-- esto es un comentario hasta el final de la línea

Los comentarios multilínea:

--[[
esto es un comentario que puede abarcar varias líneas
]]--

Trazas en consola

Antes de terminar esta primera parte dedicada al lenguaje Lua, me gustaría indicar, que como en casi todos los lenguajes y entornos de programación, disponemos de una consola para sacar mensajes, trazas, etc.
La rutina que tiene Lua para sacar un mensaje en la consola es print(). Es una función global y por tanto podemos acceder a ella desde cualquier punto de nuestro programa:

    print("Hola mundo")

Pero, ¿dónde salen los mensajes? Cuando abrimos el simulador de Corona en Windows, se abren dos ventanas y una de ellas es la consola que tiene un aspecto similar a esto:
Aquí podemos ver los mensajes de print()

La función print() puede recibir múltiples argumentos separados por comas:

    print("La edad es", 27, ", y el nombre es",nombre)