VISITAS:

viernes, 14 de febrero de 2014

Programación funcional en LUA

Las funciones como valores

En LUA, las funciones son valores con las mismas propiedades que el resto de valores (strings, números, tablas, etc). Las funciones se pueden almacenar en variables (globales o locales) y en tablas. Se pueden pasar funciones como argumentos y se pueden retornar funciones.
Una propiedad muy importante de las funciones en LUA es que tienen "lexical scoping". Esto significa que una función puede acceder a las variables de las funciones que le rodean. O sea, si una función f1 contiene internamente a otra función f2(), entonces todas las variables que se declaran en el scope de f1 pueden ser accedidas desde la función f2. Esto que parece algo sin importancia, se convertirá en LUA en algo fundamental para la programación funcional.
Las funciones en LUA son objetos anónimos, como el resto de valores, o sea, no tienen nombre. Cuando hablamos de la función print, realmente estamos hablando de una variable a la que se le ha asignado una función (valor) que muestra texto por consola.
Habitualmente, cuando escribimos una función, lo hacemos con la siguiente sintaxis:
function suma(a,b)
    return a + b;
end
Sin embargo, esto es una forma de escribir lo que realmente estamos haciendo, que es lo siguiente:
suma = function (a,b)
    return a + b;
end
Por consiguiente, una definición de función es realmente una sentencia de asignación que crea un valor de tipo "function" y lo asigna a  una variable.
Aunque lo normal es construir un valor "function" y asignarlo a una variable, hay ocasiones en que nos interesa construir la función y que permanezca anónima. Veamos un ejemplo.
La librería table ofrece una función table.sort() que recibe como argumento una tabla y ordena sus elementos. Esta función debe permitir ordenación ascendente, descendente, numérica, por fecha, etc de los elementos de la tabla. Para ello, en lugar de existir cientos de variantes de esta función, lo que se hace es pasar un segundo argumento (opcional) que es una función para comparar dos valores. Por ejemplo, supongamos la siguiente tabla de alumnos con sus notas:
alumnos = {
    { nombre="Luis", nota=6.3 },
    { nombre="Juan", nota=8.6 },
    { nombre="María", nota=9.8 }
};
Para ordenar esta tabla por nombre de alumno:
table.sort(alumnos, function (a1, a2) return a1.nombre < a2.nombre; end );
También podemos ordenar la tabla por nombre pero de forma descendente:
table.sort(alumnos, function (a1, a2) return a1.nombre > a2.nombre; end );
Las funciones que reciben como argumento otra función se denominan high-order functions.
Otro ejemplo de función high-order es el cálculo de la derivada de una función matemática en un punto. La derivada de una función f() en un punto es ( f(x+d) - f(x) ) / d
function derivada(f, d)
    d = d or 0.0001;
    return function (x)
        return (f(x+d) - f(x)) / d;
    end
end
De esta forma, podemos tener la función derivada de sen(x):
d_seno = derivada(math.sin);
Las funciones se pueden almacenar no sólo en variables globales, sino también en variables locales e incluso en tablas (esto dará lugar a los módulos y programación orientada a objetos en LUA).

Closures

Cuando escribimos una función dentro de otra función, la función interna tiene acceso a todas las variables de la función contenedora, incluidas las variables locales. A esta característica le llamamos lexical-scoping. Aunque esto suena bastante obvio, realmente no lo es, si lo combinamos con el hecho de que las funciones son valores como el resto de valores del lenguaje.
Veamos un ejemplo:
newCounter = function ()
    local i = 0;
    return function ()
        i = i + 1;    -- incrementa la variable local externa i
        return i;
    end
end
Esta función (que se asigna a la variable newCounter) crea y retorna una función anónima que incrementa el valor de la variable i (local a la función contenedora). ¿Qué ocurre cuando utilizamos newCounter para crear nuevas funciones? Cuando invoquemos a la nueva función construida, ¿cómo accederá a la variable i?
Veamos un ejemplo de uso:
func1 = newCounter();
print(func1());     -- 1
print(func1());     -- 2
Lo interesante en este ejemplo es que cuando invocamos a la función construida y retornada por newCounter(), se ejecuta una función que accede a una variable que era interna a su función contenedora, la variable i. Pero entonces, esa variable i, ya ha desaparecido ¿no? Ya que era una variable local a la función newCounter() y newCounter ya no existe. ¿Que ha ocurrido?
LUA maneja esta situación adecuadamente, mediante el concepto de closure. Para simplificar, un closure es una función más todo el entorno que la rodea. En este caso, la función retornada por newCounter() es un closure, que incluye la propia función anónima y la variable i.
Por ejemplo, si creamos otra nueva función anónima volviendo a invocar a newCounter(), se vuelve a retornar un closure nuevo con otra variable i:
func2 = newCounter();
print(func2());    -- 1
print(func1());    -- 3
print(func2());    -- 2
Esto ocurre porque func1 y func2 son dos closures distintos, cada uno con su propia instancia de la variable i.
Hablando técnicamente, los valores son closures, no las funciones.
Si la función newCounter() recibiera por ejemplo un argumento, ese argumento pasaría al closure:
newIncrementer = function (inc)
    local i = 0;
    return function ()
        i = i + inc;
        return i;
    end
end
En este caso, el closure incluye la función anónima, la variable i y el argumento inc.
Un ejemplo aclaratorio de los closures:
a = { };local x = 20;for i = 1, 10 do    local y = 0;    a[i] = function() y = y + 1; return x + y; end;end
En este ejemplo, el bucle for crea 10 closures (10 instancias de una función anónima). Cada uno de estos closures tiene una instancia de la variable y que es distinta. Sin embargo, todos los closures comparten la misma variable x.

Funciones en las tablas

Una consecuencia obvia de que las funciones (realmente closures) son valores como otro cualquiera es que podemos almacenarlas en variables locales, variables globales y en tablas.
Almacenar funciones en tablas es algo muy común en LUA. Por ejemplo, cuando llamamos a math.sin(2.56) estamos invocando a un miembro de la tabla math que se llama sin y es una función.
Para crear una tabla que tenga miembros función hacemos lo siguiente:
util = { };
util.suma = function (x, y) return x + y; end
util.resta = function (x, y) return x - y; end
De igual forma, también podemos utilizar el constructor de la tabla:
util = {
    suma = function (x, y) return x + y; end,
    resta = function (x, y) return x - y; end
};
Además, LUA ofrece una tercera alternativa para tablas con miembros función:
util = { };
function util.suma(x, y) return x + y; end
function util.resta(x, y) return x - y; end
En cualquiera de los casos, la invocación a las funciones de la tabla util sería de la siguiente forma:
util.suma(3, 2);
util.resta(4,7);

Funciones locales

Cuando almacenamos una función en una variable local, dichas funciones sólo pueden ser utilizadas en el scope en que se definen:
local func1 = function (a, b) return a ^ b; end
local func2 = function (x) return f(x, x); end
Esto es muy útil en los packages, donde las funciones del package (scope) pueden utilizar las otras funciones locales








martes, 11 de febrero de 2014

iOS: Certificados y perfiles de provisión

Introducción

Desarrollar programas para iOS (iPod, iPhone o iPad) tiene, además de lo que es el propio programa, otras tareas "accesorias" que a veces complican bastante el despliegue de la aplicación en dispositivos reales.
Apple adoptó la filosofía de que sólo Apple da permiso para que una aplicación corra en dispositivos iOS. Esto es algo bastante estricto, pero da muchas ventajas a los usuarios, ya que así se garantiza que las aplicaciones que se instalan han pasado por un control de Apple.

Certificados digitales de Apple

Pero, si sólo Apple puede autorizar aplicaciones, ¿cómo podemos desarrollar una aplicación nosotros?
La idea es que los desarrolladores tengan un certificado reconocido (autorizado) por Apple y que firmen las aplicaciones, de forma que los dispositivos vean que la firma está avalada por Apple.
Cuando un desarrollador se apunta al programa de desarrolladores de Apple, puede solicitar un certificado firmado por el propio Apple que se puede utilizar para firmar aplicaciones. Para ello, el desarrollador tiene que generar un CSR (certificate signing request) mediante la aplicación de llaveros de MAC OS.
La aplicación de llaveros genera automáticamente dos claves: una privada y una pública. El CSR lleva la clave pública, además del nombre y mail del desarrollador y se firma utilizando la clave pública. El CSR se envía a Apple (http://developer.apple.com). Como el CSR está firmado utilizando la clave privada, Apple se asegura que realmente viene del desarrollador.
Cuando Apple recibe el CSR, emite un certificado firmado por Apple. El certificado emitido por Apple contiene los mismos datos (incluida la clave pública) pero con la firma de Apple.
El certificado emitido por Apple (fichero .cer) lo descargamos a nuestro equipo y lo arrastramos (drag and drop) a la aplicación de llaveros.

Perfiles de provisión

Ya tenemos un certificado firmado por Apple con el que podremos firmar nuestras aplicaciones. Pero los dispositivos todavía no saben si pueden confiar en ti, y para esto surgen los perfiles de provisión.
Cuando creamos un perfil de provisión, lo que estamos haciendo es asociar una serie de dispositivos al certificado que hemos generado en el apartado anterior. El perfil de provisión es un fichero .provision se utiliza durante el proceso de compilación de una aplicación iOS y se despliega también en el dispositivo. Para crear un fichero de provisión hay que hacerlo en la web de desarrollo de Apple y descargarlo en el equipo. Luego se hace doble click y lo coge el Organizer de XCode.
Se pueden tener varios perfiles de provisión, uno para cada aplicación. Esto es lo normal.
En resumen, el perfil de provisión dice que el código compilado por nosotros se le permite ejecutar en una serie de dispositivos.

Compilación y ejecución de la aplicación

A la hora de compilar, lo importante es decirle a XCode que utilice nuestro certificado y el perfil de provisión.
Si miramos lo que contiene una aplicación compilada, podemos ver el perfil de provisión (que es una copia del original) y un directorio denominado _CodeSignature que contiene un fichero llamado CodeResources que es una lista de hashes de todos los ficheros del proyecto (realmente, firmas con nuestro certificado de todos los ficheros del proyecto).
Cuando se instala la aplicación, iOS hace una serie de comprobaciones: busca el fichero de provisión firmado por Apple, chequea los hashes en CodeResources utilizando la clave pública que viene en el certificado (asegurándose así que los ficheros no se han modificado durante el proceso).
Al ejecutar la aplicación, iOS vuelve a chequear el perfil de provisión y los hashes de todos los ficheros.

Para terminar...

Se necesitan dos certificados, uno para desarrollo y otro para distribución. Estos certificados se pueden utilizar para cualquier número de aplicaciones. Los certificados garantizan que la aplicación la ha desarrollado alguien reconocido por Apple, y además, garantizan que la aplicación no se ha modificado o corrompido.
El desarrollo de una aplicación para iOS tiene tres etapas:
  1. Desarrollo
  2. AdHoc
  3. Distribución
Cada fase tiene tres partes (aunque no todas las partes se aplican a todas las etapas):
  • Ficheros fuente
  • Certificados
  • Perfiles de provisión
Algunas de estas partes tienen elementos dentro de ellas:

  • Nombres de desarrolladores: son los que desarrollan la aplicación y la firman
  • UDIDs de dispositivos: son identificadores de dispositivos que se pueden utilizar para testear una aplicación (Apple permite un máximo de 100 dispositivos por desarrollador registrado).
  • AppIDs de aplicación