Erlang I
Erlang I
Erlang I
Erlang/OTP
Erlang/OTP, Volumen I: Un Mundo Concurrente por Manuel ngel Rubio 1 Jimnez se encuentra bajo una Licencia Creative Commons Reconocimiento2 NoComercial-CompartirIgual 3.0 Unported .
1 2
http://erlang.bosqueviejo.net/ http://creativecommons.org/licenses/by-nc-sa/3.0/
Tabla de contenidos
Prlogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix 1. Acerca del autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix 2. Acerca de los Revisores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x 3. Acerca del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi 4. Objetivo del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xii 5. A quin va dirigido este libro? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii 6. Estructura de la coleccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii 7. Nomenclatura usada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv 8. Agradecimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv 9. Ms informacin en la web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvi 1. Lo que debes saber sobre Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1. Qu es Erlang? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2. Caractersticas de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3. Historia de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 4. Desarrollos con Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 4.1. Sector empresarial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 4.2. Software libre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 5. Erlang y la Concurrencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 5.1. El caso de Demonware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 5.2. Yaws contra Apache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2. El lenguaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1. Tipos de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.1. tomos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.2. Nmeros Enteros y Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.3. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.4. Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.5. Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.6. Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2. Imprimiendo por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3. Fechas y Horas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3. Expresiones, Estructuras y Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 1. Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 1.1. Expresiones Aritmticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 1.2. Expresiones Lgicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 1.3. Precedencia de Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 2. Estructuras de Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 2.1. Concordancia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 2.2. Estructura case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.3. Estructura if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.4. Listas de Comprensin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3. Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 3.1. Recoger excepciones: catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
iii
Erlang/OTP
3.2. Lanzar una excepcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.3. La estructura try...catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.4. Errores de ejecucin ms comunes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4. Las funciones y mdulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 1. Organizacin del cdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 2. mbito de las funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3. Polimorfismo y Concordancia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4. Guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5. Clausuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 6. Programacin Funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 7. Recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 7.1. Ordenacin por mezcla (mergesort) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2. Ordenacin rpida (quicksort) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 8. Funciones Integradas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 5. Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 1. Anatoma de un Proceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 2. Ventajas e inconvenientes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3. Lanzando Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4. Bautizando Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5. Comunicacin entre Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 6. Procesos Enlazados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 7. Monitorizacin de Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 8. Recarga de cdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 9. Gestin de Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 10. Nodos Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 11. Procesos Remotos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 12. Procesos Locales o Globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 13. RPC: Llamada Remota a Proceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 14. Diccionario del Proceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 6. ETS, DETS y Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 1. ETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 1.1. Tipos de Tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 1.2. Acceso a las ETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 1.3. Creacin de una ETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 1.4. Lectura y Escritura en ETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 1.5. Match: bsquedas avanzadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 1.6. Eliminando tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 1.7. ETS a fichero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 2. DETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 2.1. Tipos de Tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 2.2. Crear o abrir una DETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 2.3. Manipulacin de las DETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 2.4. De ETS a DETS y viceversa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 3. Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 3.1. Abriendo y Cerrando Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 3.2. Lectura de Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 3.3. Escritura de Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
iv
Erlang/OTP
3.4. Lectura de Ficheros Binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5. Escritura de Ficheros Binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6. Acceso aleatorio de Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7. Lecturas y Escrituras por Lotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Gestin de Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1. Nombre del fichero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Copiar, Mover y Eliminar Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Permisos, Propietarios y Grupos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Gestin de Directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1. Directorio de Trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. Creacin y Eliminacin de Directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3. Es un fichero? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4. Contenido de los Directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Comunicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Conceptos bsicos de Redes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1. Direcciones IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Puertos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Servidor y Cliente UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Servidor y Cliente TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Servidor TCP Concurrente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Ventajas de inet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8. Ecosistema Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Iniciar un Proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1. Instalar rebar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Escribiendo el Cdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Compilar y Limpiar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Creando y lanzando una aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Liberar y Desplegar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Actualizando en Caliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Guiones en Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8. El camino a OTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Apndices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A. Instalacin de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Instalacin en Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Instalacin en sistemas GNU/Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1. Desde Paquetes Binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2. Compilando el Cdigo Fuente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Otros sistemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B. La lnea de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Mdulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Histrico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Directorio de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Modo JCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
103 105 105 106 107 107 108 109 110 111 111 112 113 114 114 115 117 118 123 125 127 131 131 132 133 135 136 137 140 146 150 152 153 154 154 155 156 156 157 158 158 159 160 160 161 162 162
Erlang/OTP
8. Salir de la consola . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C. Herramientas grficas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Barra de herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Monitor de aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Gestor de procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Visor de tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Depurador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
vi
Prlogo
Conoc a Manuel Angel cuando me explic su idea de escribir un libro sobre Erlang en Castellano, no slo me pareci una idea apasionante, sino un hito imprescindible para llevar este lenguaje de programacin a donde se merece entre la comunidad castellano-parlante. Tras intercambiar algunos emails, enseguida me di cuenta de la similitud de nuestras ideas y objetivos: escribir programas eficientes y escalables. Y aunque no lo conoca personalmente, simplemente con ver su dilatada experiencia en un abanico tan amplio de tecnologas, ya intu que el material que saldra de su cabeza sera de ayuda para todo tipo lectores. En un mundo donde predomina la programacin imperativa, los lenguajes funcionales vuelven a cobrar importancia por su potencia y sencillez. La necesidad de sistemas que sean capaces de gestionar millones de usuarios concurrentes de manera eficiente, ha provocado que Erlang sea relevante dos dcadas despus de su creacin. Mi primera experiencia con Erlang fue como ver Matrix, teniendo en cuenta que todos los conocimientos que tena estaban basados en lenguajes orientados a objetos, el primer instinto fue extrapolarlos a aquel primer reto al que me enfrentaba (iterar sobre una lista). Con el paso de los das empec a comprender que el salto que estaba realizando no era como aprender otro lenguaje ms (saltar entre PHP, Java o Ruby), estaba aprendiendo una nueva forma de pensar y resolver problemas la esencia de los lenguajes funcionales. Cabe destacar, que los conceptos y herramientas que proporciona de manera nativa Erlang, te permiten disear y desarrollar desde el inicio sistemas robustos, evitando tener que resolver problemas de escalabilidad y operaciones complejas en las siguientes fases de un proyecto (capas de cache complejas, despliegues en produccin sin interrupciones, optimizacin de la mquina virtual, ...). La introduccin al lenguaje propuesta por Manuel Angel, desde la base, pasando por los tipos de datos y expresiones, y terminando con las funcionalidades nativas que lo diferencian, ayudarn tanto a lectores noveles, como a lectores con experiencia en programacin funcional. Cuando alguien me comenta que quiere aprender Erlang suelo decir tres cosas: 1. Intenta con todas tus fuerzas olvidar todo lo que sepas de programacin imperativa; 2. Lee un buen libro, completo, desde la introduccin hasta las reseas;
vii
Prlogo
3. Ten siempre una consola a mano para ir poniendo en prctica los conocimientos adquiridos. Hasta el momento de la publicacin de este libro, ese consejo estaba muy condicionado al conocimiento de ingls de la persona que lo reciba; y si sumamos todos los nuevos conceptos al que el lector se enfrenta, el resultado no siempre era el esperado. Gracias a este libro, con un estilo claro y directo, ejemplos tiles y el reflejo de la experiencia del autor, har que aprender este lenguaje sea una experiencia productiva, de la que espero nazcan desde simples algoritmos rpidos y eficientes, hasta sistemas distribuidos altamente escalables. Jos Luis Gordo Romero
viii
Introduccin
Sorprendernos por algo es el primer paso de la mente hacia el descubrimiento. Louis Pasteur
ix
Introduccin
imposible en otros lenguajes. Igualmente comprobamos la simpleza, efectividad y la capacidad de escalado que brinda el lenguaje y su mquina virtual. Con todo lo aprendido y hecho en torno a este lenguaje, me he dado cuenta de que hace falta llenar el hueco que deja el no tener literatura sobre Erlang en nuestro idioma, y de paso tratar los temas desde otro punto de vista. Puedo lanzarme a esta tarea no sin antes recomendar la literatura existente que me ha servido como referencia durante mi propio proceso de aprendizaje y paso a enumerar. El libro de Joe Armstrong sobre Erlang, completsimo y centrado en el lenguaje base que he reledo decenas de veces. El de Francesco Cesarini, igualmente recomendable, aunque ms orientado al desarrollo de proyectos en Erlang. Incluso otro gran libro 1 que he conocido recientemente del equipo que mantiene Erlware , muy orientado al framework OTP y la metodologa que propone.
http://erlware.com/
Introduccin
de proyectos internacionales y entornos open source de lo ms diversos. He sido compaero de trabajo de Manuel, lo que ha servido para un enriquecimiento mutuo, tanto en conocimientos como en estrategia. Su amplio espectro de conocimiento aporta muchos patrones y antipatrones, su carcter templado hace que siempre se pueda llegar a acuerdos y sus indicaciones, especialmente en el procesamiento a tiempo real y de sistemas con alto nmero de transacciones me han sido de gran inters. Me gusta participar en proyectos estimulantes y la edicin de este libro junto con el aprendizaje de Erlang, era una oportunidad de divertirme haciendo una tarea nueva como es la edicin de literatura tcnica en espaol que no estaba dispuesto a dejar pasar. He realizado la edicin de varios captulos y ha resultado ser una experiencia ms que interesante. Ponerse en la piel de un lector y facilitarle las cosas sin bajar demasiado el nivel tcnico es un reto. Espero que el resultado, probablemente mejorable, facilite la lectura y comprensin de la obra y por ende su difusin. El presente libro permite formarse en programacin concurrente en Erlang de una forma entretenida. Ya estoy deseando leer la segunda parte sobre OTP, ya que intuyo que para proyectos de cierta envergadura se requieren unas directrices claras y el uso de buenos patrones de diseo, sobre todo si se busca la robustez que normalmente requieren proyectos crticos que en muchas ocasiones manejan transacciones monetarias.
xi
Introduccin
el libro que me hubiese gustado encontrar. Un libro con las palabras justas y los diagramas apropiados para poder entender ms rpidamente todos los conceptos nuevos que se ponen delante del programador de Erlang y OTP. Por ltimo, el hecho de que el texto est en castellano hace que, sin duda, sea ms asequible para el pblico de habla hispana. El nivel y densidad de ciertas explicaciones son ms bajos cuando se tratan en el idioma nativo, lo que hace que sea ms fcil de entender.
xii
Introduccin
nuevas herramientas que va ganando cada vez ms relevancia en los entornos mencionados.
6. Estructura de la coleccin
Al principio pens en escribir un nico libro orientado a Erlang, pero viendo el tamao que estaba alcanzando pens que mejor era dividirlo por temtica y darle a cada libro la extensin apropiada como para ser ledo y consultado de forma fcil y rpida. La coleccin, por tanto, consta de dos volmenes. Cada volumen tiene como misin explorar Erlang de una forma diferente, desde un punto de vista diferente, y con un objetivo diferente. Los volmenes son:
xiii
Introduccin
Un mundo concurrente. En esta parte nos centraremos en conocer la sintaxis del lenguaje, sus elementos ms comunes, sus estructuras, los tipos de datos, el uso de los ficheros y comunicaciones a travs de la red. Ser el bloque ms extenso, ya que detalla toda la estructura del lenguaje en s. Las bases de OTP. Nos adentramos en el conocimiento del sistema OTP, el framework actualmente ms potente para Erlang y que viene con su instalacin base. Se vern los generadores de servidores, las mquinas de estados, los supervisores y manejadores de eventos, entre otros elementos.
Nota
Recomiendo que, para poder hacer los ejemplos y practicar lo que se va leyendo, se tenga a mano un ordenador con Erlang instalado, as como acceso a su consola y un directorio en el que poder ir escribiendo los programas de ejemplo. En este caso ser de bastante ayuda revisar los apndices donde explica cmo se descarga, instala y usa la consola de Erlang, as como la compilacin de los ejemplos y su ejecucin de forma bsica.
7. Nomenclatura usada
A lo largo del libro encontrars muchos ejemplos y fragmentos de cdigo. Los cdigos aparecen de una forma visible y con un formato distinto al del resto del texto. Tendrn este aspecto:
-module(hola). mundo() -> io:format("Hola mundo!~n", []).
Adems de ejemplos con cdigo Erlang, en los distintos apartados del libro hay diferentes bloques que contienen notas informativas o avisos importantes. Sus formatos son los siguientes:
Nota
Esta es la forma que tendrn las notas informativas. Contienen detalles o informacin adicional sobre el texto para satisfacer la curiosidad del lector.
Importante
Estas son las notas importantes que indican usos especficos y detalles importantes que hay que tener muy en cuenta. Se recomienda su lectura.
xiv
Introduccin
8. Agradecimientos
Manuel ngel Rubio Agradecer a mi familia, Marga, Juan Antonio y Ana Mara, por ser pacientes y dejarme el tiempo suficiente para escribir, as como su amor y cario. A mis padres por ensearme a defenderme en esta vida, as como a competir conmigo mismo para aprender y superarme en cada reto personal y profesional. Respecto al libro, he de agradecer al equipo con el que estuve trabajando en Jet Multimedia: Guillermo Rodrguez, Mara Luisa de la Serna, Jonathan Mrquez, Margarita Ortiz y Daniel Lpez; el que cada desarrollo que nos planteasen pudisemos verlo como un desafo a nosotros mismos y sacar lo mejor de nosotros mismos, as como aprender de cada situacin, de cada lenguaje y de cada herramienta. Aprend mucho con ellos y espero que podamos seguir aprendiendo all donde nos toque estar y, si volvemos a coincidir, muchsimo mejor. Tambin agradecer a Jos Luis Gordo por su revisin, el prlogo escrito y sus buenos consejos as como su crtica constructiva, ha sido un aliado inestimable en esta aventura y un baln de oxgeno en momentos arduos. A Juan Sebastin Prez, por brindarse tambin a aprender el lenguaje de manos de este manuscrito, as como corregir tambin mi forma de expresarme en algunos puntos que confieso fueron complicados. Por ltimo pero no por ello menos importante, agradecer a mi hermano Rafael y a Luz (Bethany Neumann) el diseo de la portada y contraportada del libro, as como el logotipo de BosqueViejo. Jos Luis Gordo Agradecer a Manuel Angel su confianza por haberme dejado aportar mi pequeo granito de arena a este proyecto. Adems de ampliar conocimientos, me ha dado la oportunidad de conocer mejor su trabajo y a l personalmente, descubriendo su increble energa y motivacin, sin la cual este libro nunca hubiera visto la luz. Juan Sebastin Prez Gracias a Manuel por compartir tantos cafs (descafeinados) e ideas. A otros compaeros en lo profesional y personal, especialmente al departamento de movilidad de Jet Multimedia por compartir fatigas y xitos. Y como no, a mi familia, amigos y pareja que me han
xv
Introduccin
9. Ms informacin en la web
Para obtener informacin sobre las siguientes ediciones, fe de erratas y comentarios, contactos, ayuda y dems sobre el libro Erlang/OTP he habilitado una seccin en mi web. El sitio web: http://erlang.bosqueviejo.net
xvi
1. Qu es Erlang?
Para comprender qu es Erlang, debemos entender que se trata de un entorno o plataforma de desarrollo completa. Erlang proporciona no slo el compilador para poder ejecutar el cdigo, sino que posee tambin una coleccin de herramientas, y una mquina virtual sobre la que ejecutarlo, por lo tanto existen dos enfoques: Erlang como lenguaje Hay muchas discusiones concernientes a si Erlang es o no un lenguaje funcional. En principio, est entendido que s lo es, aunque tenga elementos que le hagan salirse de la definicin pura. Por ello Erlang podra mejor catalogarse como un lenguaje hbrido, al tener elementos de tipo funcional, de tipo imperativo, e incluso algunos rasgos que permiten cierta orientacin a objetos, aunque no completa. Donde encaja mejor Erlang, al menos desde mi punto de vista, es como un lenguaje orientado a la concurrencia. Erlang tiene una gran facilidad para la programacin distribuida, paralela o concurrente y adems con mecanismos para la tolerancia a fallos. Fue diseado desde un inicio para ejecutarse de forma ininterrumpida. Esto significa que se puede cambiar el cdigo de sus aplicaciones sin detener su ejecucin. Ms adelante explicaremos cmo funciona esto concretamente.
Erlang como entorno de ejecucin Como hemos mencionado antes Erlang es una plataforma de desarrollo que proporciona no slo un compilador, sino tambin una mquina virtual para su ejecucin. A diferencia de otros lenguajes interpretados como Python, Perl, PHP o Ruby, Erlang se pseudocompila y su mquina virtual le proporciona una importante capa de abstraccin que le dota de la capacidad de manejar y distribuir procesos entre nodos de forma totalmente transparente (sin el uso de libreras especficas). La mquina virtual sobre la que se ejecuta el cdigo pseudocompilado de Erlang, que le proporciona todas las caractersticas de distribucin y comunicacin de procesos, es tambin una mquina 1 que interpreta un pseudocdigo mquina que nada tiene que ver, a ese nivel, con el lenguaje Erlang. Esto ha permitido la proliferacin de los lenguajes que emplean la mquina virtual pero no el lenguaje en s, como pueden ser: Reia, Elixir, Efene, Joxa o LFE. Erlang fue propietario hasta 1998 momento en que fue cedido como cdigo abierto (open source) a la comunidad. Fue creado inicialmente por Ericsson, ms especficamente por Joe Armstrong, aunque no slo por l. Recibe el nombre de Agnus Kraup Erlang. A veces se piensa que el nombre es una abreviacin de ERicsson LANGuage, debido a su uso intensivo en Ericsson. Segn Bjarne Dcker, jefe del Computer Science Lab en su da, esta dualidad es intencionada.
2. Caractersticas de Erlang
Durante el perodo en el que Joe Armstrong y sus compaeros estuvieron en los laboratorios de Ericsson, vieron que el desarrollo de aplicaciones basadas en PLEX no era del todo ptimo para la programacin de aplicaciones dentro de los sistemas hardware de Ericsson. Por esta razn comenzaron a buscar lo que sera un sistema de desarrollo ptimo basado en las siguiente premisas: Distribuido El sistema deba de ser distribuido para poder balancear su carga entre los sistemas hardware. Se buscaba un sistema que pudiera lanzar procesos no slo en la mquina en la que se ejecuta, sino que tambin fuera capaz de hacerlo en otras mquinas. Lo que en lenguajes como C viene a ser PVM o MPICH pero sin el uso explcito de ninguna librera.
1
Tolerante a fallos Si una parte del sistema tiene fallos y tiene que detenerse, que esto no signifique que todo el sistema se detenga. En sistemas software como PLEX o C, un fallo en el cdigo determina una interrupcin completa del programa con todos sus hilos y procesos. Hay otros lenguajes como Java, Python o Ruby que manejan estos errores como excepciones, afectando slo a una parte del programa y no a todos sus hilos. No obstante, en los entornos con memoria compartida, un error puede dejar corrupta esta memoria por lo que esa opcin no garantiza tampoco que no afecte al resto del programa. Escalable Los sistemas operativos convencionales tenan problemas en mantener un elevado nmero de procesos en ejecucin. Los sistemas de telefona que desarrolla Ericsson se basan en tener un proceso por cada llamada entrante, que vaya controlando los estados de la misma y pueda provocar eventos hacia un manejador, a su vez con sus propios procesos. Por lo que se buscaba un sistema que pudiese gestionar desde cientos de miles, hasta millones de procesos. Cambiar el cdigo en caliente Tambin es importante en el entorno de Ericsson, y en la mayora de sistemas crticos o sistemas en produccin de cualquier ndole, que el sistema no se detenga nunca, aunque haya que realizar actualizaciones. Por ello se agreg tambin como caracterstica el hecho de que el cdigo pudiese cambiar en caliente, sin necesidad de parar el sistema y sin que afectase al cdigo en ejecucin. Tambin haba aspectos ntimos del diseo del lenguaje que se quisieron tener en cuenta para evitar otro tipo de problemas. Aspectos tan significativos como: Asignaciones nicas Como en los enunciados matemticos la asignacin de un valor a una variable se hace una nica vez y, durante el resto del enunciado, esta variable mantiene su valor inmutable. Esto nos garantiza un mejor seguimiento del cdigo y una mejor deteccin de errores. Lenguaje simple Para rebajar la curva de aprendizaje el lenguaje debe de tener pocos elementos y ninguna excepcin. Erlang es un lenguaje simple de comprender y aprender, ya que tiene nada ms que dos estructuras de control, carece de bucles y emplea tcnicas como la recursividad y modularizacin para conseguir algoritmos pequeos y eficientes.
Las estructuras de datos se simplifican tambin bastante y su potencia, al igual que en lenguajes como Prolog o Lisp, se basa en las listas. Orientado a la Concurrencia Como una especie de nueva forma de programar, este lenguaje se orienta a la concurrencia de manera que las rutinas ms ntimas del propio lenguaje estn preparadas para facilitar la realizacin de programas concurrentes y distribuidos. Paso de mensajes en lugar de memoria compartida Uno de los problemas de la programacin concurrente es la ejecucin de secciones crticas de cdigo para acceso a porciones de memoria compartida. Este control de acceso acaba siendo un cuello de botella ineludible. Para simplificar e intentar eliminar el mximo posible de errores, Erlang/OTP se basa en el paso de mensajes en lugar de emplear tcnicas como semforos o monitores. El paso de mensajes hace que un proceso sea el responsable de los datos y la seccin crtica se encuentre slo en este proceso, de modo que cualquiera que pida ejecutar algo de esa seccin crtica, tenga que solicitrselo al proceso en cuestin. Esto abstrae al mximo la tarea de desarrollar programas concurrentes, simplificando enormemente los esquemas y eliminando la necesidad del bloque explcito. Hace no mucho encontr una presentacin bastante interesante sobre 2 Erlang , en la que se agregaba, no slo todo lo que comentaba Armstrong que deba de tener su sistema para poder desarrollar las soluciones de forma ptima, sino tambin la contraposicin, el porqu no lo pudo encontrar en otros lenguajes. En principio hay que entender que propsito general se refiere al uso generalizado de un lenguaje a lo ms cotidiano que se suele desarrollar. Como es obvio, es ms frecuente hacer un software para administracin de una empresa que un sistema operativo. Los lenguajes de propsito general sern ptimos para el desarrollo general de ese software de gestin empresarial, seguramente no tanto para ese software del sistema operativo. PHP por ejemplo, es un fabuloso lenguaje de marcas que facilita bastante la tarea a los desarrolladores web y sobre todo a maquetadores que se meten en el terreno de la programacin. Pero es algo completamente desastroso para el desarrollo de aplicaciones de scripting para administradores de sistemas. En s, los lenguajes ms difundidos hoy en da, como C# o Java, presentan el problema de carecer de elementos a bajo nivel integrados en sus
2
http://www.it.uu.se/edu/course/homepage/projektDV/ht05/uppsala.pdf
sistemas que les permitan desarrollar aplicaciones concurrentes de forma fcil. Esta es la razn de que en el mundo Java comience a hacerse cada vez ms visible un lenguaje como Scala.
3. Historia de Erlang
Joe Armstrong asisti a la conferencia de Erlang Factory de Londres, en 2010, donde explic la historia de la mquina virtual de Erlang. En s, 3 es la propia historia de Erlang/OTP. Sirvindome de las diapositivas que proporcion para el evento, vamos a dar un repaso a la historia de Erlang/ OTP. La idea de Erlang surgi por la necesidad de Ericsson de acotar un problema que haba surgido en su plataforma AXE, que estaba siendo desarrollada en PLEX, un lenguaje propietario. Joe Armstrong junto a dos colegas, Elshiewy y Robert Virding, desarrollaron una lgica concurrente de programacin para canales de comunicacin. Esta lgebra de telefona permita a travs de su notacin describir el sistema pblico de telefona (POTS) en tan slo quince reglas. A travs del inters de llevar esta teora a la prctica desarrollaron modelos en Ada, CLU, Smalltalk y Prolog entre otros. As descubrieron que el lgebra telefnica se procesaba de forma muy rpida en sistemas de alto nivel, es decir, en Prolog, con lo que comenzaron a desarrollar un sistema determinista en l. La conclusin a la que lleg el equipo fue que, si se puede resolver un problema a travs de una serie de ecuaciones matemticas y portar ese mismo esquema a un programa de forma que el esquema funcional se respete y entienda tal y como se formul fuera del entorno computacional, puede ser fcil de tratar por la gente que entiende el esquema, incluso mejorarlo y adaptarlo. Las pruebas realmente se realizan a nivel terico sobre el propio esquema, ya que algortmicamente es ms fcil de probarlo con las reglas propias de las matemticas que computacionalmente con la cantidad de combinaciones que pueda tener. Prolog no era un lenguaje pensado para concurrencia, por lo que se decidieron a realizar uno que satisfaciera todos sus requisitos, basndose en las ventajas que haban visto de Prolog para conformar su base. Erlang vi la luz en 1986, despus de que Joe Armstrong se encerrase a desarrollar la idea base como intrprete sobre Prolog, con un nmero reducido de instrucciones que rpidamente fue creciendo gracias a su buena acogida. Bsicamente, los requisitos que se buscaban cumplir eran:
3
http://www.erlang-factory.com/upload/presentations/247/erlang_vm_1.pdf
Los procesos deban de ser una parte intrnseca del lenguaje, no una librera o framework de desarrollo. Deba poder ejecutar desde miles a millones de procesos concurrentes y cada proceso ser independiente del resto, de modo que si alguno de ellos se corrompiese no daase el espacio de memoria de otro proceso. Este requisito nos lleva a que el fallo de los procesos debe de ser aislado del resto del programa. Debe poder ejecutarse de modo ininterrumpido, lo que obliga a que para actualizar el cdigo del sistema no se deba detener su ejecucin, sino que se recargue en caliente. En 1989, el sistema estaba comenzando a dar sus frutos, pero surgi el problema de que su rendimiento no era el adecuado. Se lleg a la conclusin de que el lenguaje era adecuado para la programacin que se realizaba, pero tendra que ser, al menos unas 40 veces ms rpido. Mike Williams se encarg de escribir el emulador, cargador, planificador y recolector de basura (en lenguaje C) mientras que Joe Armstrong escriba el compilador, las estructuras de datos, el heap de memoria y la pila; por su parte Robert Virding se encargaba de escribir las libreras. El sistema desarrollado se optimiz a un nivel en el que consiguieron aumentar su rendimiento en 120 veces de lo que lo haca el intrprete en Prolog. En los aos 90, tras haber conseguido desarrollar productos de la gama AXE con este lenguaje, se le potenci agregando elementos como distribucin, estructura OTP, HiPE, sintaxis de bit o compilacin de patrones para matching. Erlang comenzaba a ser una gran pieza de software, pero tena varios problemas para que pudiera ser adoptado de forma amplia por la comunidad de programadores. Desafortunadamente para el desarrollo de Erlang, aqul periodo fue tambin la dcada de Java y Ericsson decidi centrarse en lenguajes usados globalmente por lo que prohibi seguir desarrollando en Erlang.
Nota
HiPE es el acrnimo de High Performance Erlang (Erlang de Alto Rendimiento) que es el nombre de un grupo de investigacin sobre Erlang formado en la Universidad de Uppsala en 1998. El grupo desarroll un compilador de cdigo nativo de modo que la mquina (BEAM) virtual de Erlang no tenga que interpretar ciertas partes del cdigo si ya estn en lenguaje mquina mejorando as su rendimiento.
Con el tiempo, la imposicin de no escribir cdigo en Erlang se fue olvidando y la comunidad de programadores de Erlang comenz a crecer fuera de Ericsson. El equipo OTP se mantuvo desarrollando y soportando
Erlang que, a su vez, continu como sufragador del proyecto HiPE y aplicaciones como EDoc o Dialyzer. Antes de 2010 Erlang agreg capacidad para SMP y ms recientemente para multi-core. La revisin de 2010 del emulador de BEAM se ejecuta con un rendimiento 300 veces superior al de la versin del emulador en C, por lo que es 36.000 veces ms rpido que el original interpretado en Prolog. Cada vez ms sectores se hacen eco de las capacidades de Erlang y cada vez ms empresas han comenzado desarrollos en esta plataforma por lo que se augura que el uso de este lenguaje siga al alza.
http://www.erlang-factory.com/conference/London2011/speakers/MalcolmDowse http://es.slideshare.net/wooga/erlang-the-big-switch-in-social-games
WhatsApp, la aplicacin actualmente ms relevante para el intercambio y envo de mensajes entre smartphones emplea a nivel de servidor sistemas desarrollados en Erlang. Una de las empresas estandarte de Erlang ha sido Kreditor, que cambi 6 su nombre a Klarna AB . Esta empresa se dedica al pago por Internet y pas en 7 aos a tener 600 empleados. En el terreno del desarrollo web comienzan a abrirse paso tambin 7 empresas espaolas como Mikoagenda . Es un claro ejemplo de desarrollo de aplicaciones web ntegramente desarrolladas con Erlang a nivel de servidor. Desde que surgi el modelo Cloud, cada vez ms empresas de software estn prestando servicios online en lugar de vender productos, por lo que se enfrentan a un uso masificado por parte de sus usuarios, e incluso a ataques de denegacin de servicio. Estos escenarios junto con servicios bastante pesados e infraestructuras no muy potentes hacen cada vez ms necesarias herramientas como Erlang. En la web de Aprendiendo Erlang mantienen un listado mixto de software libre y empresas que emplean Erlang.
8
Nota
Aprovechando que se ha comenzado a hacer esta lista de software libre desarrollado en Erlang se ha estructurado y ampliado la pgina correspondiente a Erlang en Wikipedia (en ingls de momento y poco a poco en castellano), por lo que en estos momentos ser ms extensa que la lista presente en estas pginas.
Apache CouchDB , es una base de datos documental con acceso a datos mediante HTTP y empleando el formato REST. Es uno de los proyectos que estn acogidos en la fundacin Apache. Riak , una base de datos NoSQL inspirada en Dynamo (la base de datos NoSQL de Amazon). Es usada por empresas como Mozilla y Comcast. Se basa en una distribucin de fcil escalado y completamente tolerante a fallos. SimpleDB , tal y como indica su propia web (en castellano) es un almacn de datos no relacionales de alta disponibilidad flexible que descarga el trabajo de administracin de las bases de datos. Es decir, un sistema NoSQL que permite el cambio en caliente del esquema de datos de forma fcil que realiza auto-indexacin y permite la distribucin de los datos. Fue desarrollada por Amazon. Couchbase , es una base de datos NoSQL para sistemas de misin crtica. Con replicacin, monitorizacin, tolerante a fallos y compatible con Memcached. Servidores Web Yaws . Como servidor web completo, con posibilidad de instalarse y configurarse para ello, slo existe (al menos es el ms conocido en la comunidad) Yaws. Su configuracin se realiza de forma bastante similar a Apache. Tiene unos scripts que se ejecutan a nivel de servidor bastante potentes y permite el uso de CGI y FastCGI. Frameworks Web ErlyWeb , no ha tenido modificaciones por parte de Yariv desde hace unos aos por lo que su uso ha decado. El propio Yariv lo emple para hacer un clon de twitter y se emple inicialmente para la interfaz de chat para facebook. BeepBeep , es un framework inspirado en Rails y Merb aunque sin integracin con base de datos. Erlang Web , es un sistema desarrollado por Erlang Solutions que trata igualmente las vistas y la parte del controlador pero tampoco la parte de la base de datos.
16 15 14 13 12 11 10
Nitrogen , es un framework pensado para facilitar la construccin de interfaces web. Nos permite agregar cdigo HTML de una forma simple y enlazarlo con funcionalidad de JavaScript sin necesidad de escribir ni una sola lnea de cdigo JavaScript. ChicagoBoss , quizs el ms activo y completo de los frameworks web para Erlang a da de hoy. Tiene implementacin de vistas, plantillas (ErlyDTL), definicin de rutas, controladores y modelos a 19 travs de un sistema ORM . CMS (Content Management System) Zotonic , sistema CMS que permite el diseo de pginas web de forma sencilla a travs de la programacin de las vistas (DTL) y la gestin del contenido multimedia, texto y otros aspectos a travs del interfaz de administracin. Chat ejabberd , servidor de XMPP muy utilizado en el mundo Jabber. Este servidor permite el escalado y la gestin de multi-dominios. Es usado en sitios como la BBC Radio LiveText, Ovi de Nokia, KDE Talk, Chat de Facebook, Chat de Tuenti, LiveJournal Talk, etc. Colas de Mensajes RabbitMQ , servidor de cola de mensajes muy utilizado en sistemas de entornos web con necesidad de este tipo de sistemas para conexiones de tipo websocket, AJAX o similar en la que se haga necesario un comportamiento asncrono sobre las conexiones sncronas. Fue adquirido por SpringSource, una filial de VMWare en abril de 2010.
23 22 20 21 18
5. Erlang y la Concurrencia
Una de las mejores pruebas de que Erlang/OTP funciona, es mostrar las comparaciones que empresas como Demonware o gente como el propio Joe Armstrong han realizado. Sistemas sometidos a un banco de pruebas
17 18 19
http://nitrogenproject.com/ http://www.chicagoboss.org/ Object Relational Mapping, sistema empleado para realizar la transformacin entre objetos y tablas para emplear directamente los objetos en cdigo y que la informacin que estos manejen se almacene en una tabla de la base de datos. 20 http://zotonic.com/ 21 Content Management System, Sistema de Administracin de Contenido 22 http://www.ejabberd.im/ 23 http://www.rabbitmq.com/
10
para comprobar cmo rinden en produccin real o cmo podran rendir en entornos de pruebas controlados. Comenzar por comentar el caso de la empresa Demonware, de la que ya coment algo en la seccin de uso de Erlang en el Sector empresarial, pero esta vez lo detallar con datos que aport la propia compaa a travs de Malcolm Dowse en la Erlang Factory de Londrs de 2011. Despus veremos el banco de pruebas que realiz Joe Armstrong sobre un servicio empleando un par de configuraciones de Apache y Yaws.
11
centros de datos. En palabras de Malcolm: fue una crisis para la compaa, tenamos que crecer, sin el cambio a Erlang la crisis podra haber sido un desastre. Demonware es una de las empresas que ha visto las ventajas de Erlang. La forma en la que implementa la programacin concurrente y la gran capacidad de escalabilidad. Gracias a estos factores, han podido estar a la altura de prestar el servicio de los juegos en lnea ms usados y jugados de los ltimos tiempos.
En gris oscuro (marcando el punto con un crculo y ocupando las lneas superiores del grfico) puede verse la respuesta de Yaws en escala de
24
http://www.sics.se/~joe/apachevsyaws.html
12
KB/s (eje Y) frente a carga (eje X). Las lneas que se cortan a partir de las 4 mil peticiones corresponden a dos configuraciones diferentes de Apache (en negro y gris claro). En este caso, pasa algo parecido a lo visto con Demonware en la seccin anterior, Apache no puede procesar ms de 4000 peticiones simultneas, en parte debido a su integracin ntimamente ligada al sistema operativo, que le limita. Sin embargo, Yaws se mantiene con el mismo rendimiento hasta llegar a superar las 80 mil peticiones simultneas. Erlang est construido con gestin de procesos propia y desligada del sistema operativo. En s, suele ser ms lenta que la que proporciona el sistema operativo, pero sin duda la escalabilidad y el rendimiento que se consigue pueden paliar ese hecho. Cada nodo de Erlang puede manejar en total unos 2 millones de procesos.
13
Captulo 2. El lenguaje
Slo hay dos tipos de lenguajes: aquellos de los que la gente se queja y aquellos que nadie usa. Bjarne Stroustrup Erlang tiene una sintaxis muy particular. Hay gente a la que termina gustndole y otras personas que lo consideran incmodo. Hay que entender que es un lenguaje basado en Prolog y con tintes de Lisp por lo que se asemeja ms a los lenguajes funcionales que a los imperativos. La mayora de personas comienzan programando en lenguajes como Basic, Modula-2 o Pascal, que tienen una sintaxis muy parecida entre ellos. Lo mismo pasa con la rama de C/C++, Java y Perl o PHP, que tienen una sintaxis, el uso de los bloques condicionales, iterativos y declaracin de funciones y clases tambin semejantes. En los lenguajes imperativos la sintaxis se basa en la consecucin de mandatos que el programador enva a travs del cdigo a la mquina. En Erlang y dems lenguajes funcionales, la sintaxis est diseada como si se tratara de la definicin de una funcin matemtica o una proposicin lgica. Cada elemento dentro de la funcin tiene un propsito: obtener un valor; el conjunto de todos esos valores, con o sin procesamiento, conforma el resultado. Un ejemplo bsico:
area(Base, Altura) -> Base * Altura.
En este ejemplo puede verse la definicin de la funcin area. Los parmetros requeridos para obtener su resultado son Base y Altura. A la declaracin de parmetros le sigue el smbolo de consecucin (->), como si se tratase de una proposicin lgica. Por ltimo est la operacin interna que retorna el resultado que se quiere obtener. Al tratarse de funciones matemticas o proposiciones lgicas no existe una correlacin entre imperativo y funcional. Para un cdigo imperativo comn como el que sigue:
para i <- 1 hasta 10 hacer si clavar(i) = 'si' entonces martillea_clavo(i) fsi fpara
No existe en Erlang un equivalente que pueda transcribir una accin imperativa como tal. Para desarrollar en Erlang hay que pensar en el qu se quiere hacer ms que en el cmo. Si en un lenguaje funcional lo que se quiere es clavar los clavos que seleccione la funcin clavar martilleando, se podra hacer a travs de una lista de comprensin:
14
El lenguaje
Hay que entender que para resolver problemas de forma funcional muchas veces la mentalidad imperativa es un obstculo. Tenemos que pensar en los datos que tenemos y qu datos queremos obtener como resultado. Es lo que nos conducir a la solucin. Erlang es un lenguaje de formato libre. Se pueden insertar tantos espacios y saltos de lnea entre smbolos como se quiera. Esta funcin area es completamente equivalente a la anterior a nivel de ejecucin:
area( Base, Altura ) -> Base * Altura .
A lo largo de este captulo revisaremos la base del lenguaje Erlang. Veremos lo necesario para poder escribir programas bsicos de propsito general y entender esta breve introduccin de una forma ms detallada y clara.
1. Tipos de Datos
En Erlang se manejan varios tipos de datos. Por hacer una distincin rpida podemos decir que se distinguen entre: simples y complejos; otras organizaciones podran conducirnos a pensar en los datos como: escalares y conjuntos o atmicos y compuestos. No obstante, la forma de organizarlos no es relevante con el fin de conocerlos, identificarlos y usarlos correctamente. Emplearemos la denominacin simples y complejos (o compuestos), pudiendo referirnos a cualquiera de las otras formas de categorizacin si la explicacin resulta ms clara. Como datos simples veremos en esta seccin los tomos y los nmeros. Como datos de tipo complejo veremos las listas y tuplas. Tambin veremos las listas binarias, un tipo de dato bastante potente de Erlang y los registros, un tipo de dato derivado de las tuplas.
1.1. tomos
Los tomos son identificadores de tipo carcter que se emplean como palabras clave y ayudan a semantizar el cdigo. Un tomo es una palabra que comienza por una letra en minscula y va seguido de letras en mayscula o minscula, nmeros y/o subrayados. Tambin se pueden emplear letras en mayscula al inicio, espacios y lo
15
El lenguaje
que queramos, siempre y cuando encerremos la expresin entre comillas simples. Algunos ejemplos:
> is_atom(cuadrado). true > is_atom(a4). true > is_atom(alta_cliente). true > is_atom(bajaCliente). true > is_atom(alerta_112). true > is_atom(false). true > is_atom('HOLA'). true > is_atom(' eh??? '). true
Los tomos tienen como nica finalidad ayudar al programador a identificar estructuras, algoritmos y cdigo especfico. Hay tomos que se emplean con mucha frecuencia como son: true, false y undefined. Los tomos junto con los nmeros enteros y reales y las cadenas de texto componen lo que se conoce en otros lenguajes como literales. Son los datos que tienen un significado de por s, y se pueden asignar a una variable directamente.
Nota
Como literales se pueden especificar nmeros, pero tambin valores de representaciones de la tabla de caracteres. Al igual que en otros lenguajes, Erlang permite dar el valor de un carcter especfico a travs el uso de la sintaxis: $A, $1, $!. Esto retornar el valor numrico para el smbolo indicado tras el smbolo del dlar en la tabla de caracteres.
16
El lenguaje
true
Otra de las cosas que sorprende de Erlang es su precisin numrica. Si multiplicamos nmeros muy altos veremos como el resultado sigue mostrndose en notacin real, sin usar la notacin cientfica que muestran otros lenguajes cuando una operacin supera el lmite de clculo de los nmeros enteros (o valores errneos por overflow):
> 102410241024 * 102410241024 * 1234567890. 12947972063153419287126752624640
Esta caracterstica hace de Erlang una plataforma muy precisa y adecuada para clculos de intereses bancarios, tarificacin telefnica, ndices burstiles, valores estadsticos, posicin de puntos tridimensionales, etc.
Nota
Los nmeros se pueden indicar tambin anteponiendo la base en la que queremos expresarlos y usando como separador la almohadilla (#). Por ejemplo, si queremos expresar los nmeros en base octal, lo haremos anteponiendo la base al nmero que queremos representar 8#124. Anlogamente 2#1011 representa un nmero binario y 16#f42a un nmero hexadecimal.
1.3. Variables
Las variables, como en matemticas, son smbolos a los que se enlaza un valor y slo uno a lo largo de toda la ejecucin del algoritmo especfico. Esto quiere decir que cada variable durante su tiempo de vida slo puede contener un valor. El formato de las variables se inicia con una letra mayscula, seguida de tantas letras, nmeros y subrayados como se necesiten o deseen. Una variable puede tener esta forma:
> Pi = 3.1415. 3.1415 > Telefono = "666555444". "666555444" > Depuracion = true. true
Sobre las variables se pueden efectuar expresiones aritmticas, en caso de que contenga nmeros, operaciones de listas o emplearse como parmetro en llamadas a funciones. Un ejemplo de variables conteniendo nmeros:
> Base = 2. 2
17
El lenguaje
Si en un momento dado, queremos que Base tenga el valor 3 en lugar del valor 2 inicialmente asignado veramos lo siguiente:
> Base = 2. 2 > Base = 3. ** exception error: no match of right hand side value 3
Lo que est ocurriendo es que Base ya est enlazado al valor 2 y que la concordancia (o match) con el valor 2 es correcto, mientras que si lo intentamos encajar con el valor 3 resulta en una excepcin.
Nota
Para nuestras pruebas, a nivel de consola y para no tener que salir y entrar cada vez que queramos que Erlang olvide el valor con el que se enlaz una variable, podemos emplear:
> f(Base). ok > Base = 3. 3
Para eliminar todas las variables que tenga memorizadas la consola se puede emplear: f().
La ventaja de la asignacin nica es la facilidad de analizar cdigo aunque muchas veces no se considere as. Si una variable durante toda la ejecucin de una funcin slo puede contener un determinado valor 1 el comportamiento de dicha funcin es muy fcilmente verificable .
1.4. Listas
Las listas en Erlang son vectores de informacin heterognea, es decir, pueden contener informacin de distintos tipos, ya sean nmeros, tomos, tuplas u otras listas. Las listas son una de las potencias de Erlang y otros lenguajes funcionales. Al igual que en Lisp, Erlang maneja las listas como lenguaje de alto nivel, en modo declarativo, permitiendo cosas como las listas de comprensin o la agregacin y eliminacin de elementos especficos como si de conjuntos se tratase.
1
Muestra de ello es dialyzer, una buena herramienta para comprobar el cdigo escrito en Erlang.
18
El lenguaje
A estas listas se les pueden agregar o sustraer elementos con los operadores especiales ++ y --. Tal y como se presenta en los siguientes ejemplos:
> [1,2,3] ++ [4]. [1,2,3,4]. > [1,2,3] -- [2]. [1,3]
Otro de los usos comunes de las listas es la forma en la que se puede ir tomando elementos de la cabecera de la lista dejando el resto en otra sublista. Esto se realiza con esta sencilla sintaxis:
> [H|T] = [1,2,3,4]. [1,2,3,4] > H. 1 > T. [2,3,4] > [H1,H2|T2] = [1,2,3,4]. [1,2,3,4] > H1. 1 > H2. 2 > T2. [3,4]
De esta forma tan sencilla la implementacin de los conocidos algoritmos de push y pop de insercin y extraccin en pilas resultan tan triviales como:
> Lista = []. [] > Lista2 = [1|Lista]. [1] > Lista3 = [2|Lista2]. [2,1] > [Extrae|Lista2] = Lista3. [2,1] > Extrae. 2 > Lista2.
19
El lenguaje
[1]
No obstante, el no poder mantener una nica variable para la pila dificulta su uso. Este asunto lo analizaremos ms adelante con el tratamiento de los procesos y las funciones.
Como puede apreciarse, la asignacin no da ningn error ya que ambos valores, a izquierda y derecha, son el mismo para Erlang.
Importante
Esta forma de tratar las cadenas es muy similar a la que se emplea en lenguaje C, en donde el tipo de dato char es un dato de 8 bits en el que se puede almacenar un valor de 0 a 255 y que las funciones de impresin tomarn como representaciones de la tabla de caracteres en uso por el sistema. En Erlang, la nica diferencia es que cada dato no es de 8 bits sino que es un entero lo que conlleva un mayor consumo de memoria pero mejor soporte de nuevas tablas como la de UTF-16 o las extensiones del UTF-8 y similares.
Al igual que con el resto de listas, las cadenas de caracteres soportan tambin la agregacin de elementos, de modo que la concatenacin se podra realizar de la siguiente forma:
> "Hola, " ++ "mundo!". "Hola, mundo!"
Una de las ventajas de la asignacin propia de que dispone Erlang es que si encuentra una variable que no ha sido enlazada a ningn valor, automticamente cobra el valor necesario para que la ecuacin sea cierta. Erlang intenta hacer siempre que los elementos a ambos lados del signo de asignacin sean iguales. Un ejemplo:
20
El lenguaje
Esta notacin tiene sus limitaciones, en concreto la variable no asignada debe estar al final de la expresin, ya que de otra forma el cdigo para realizar el encaje sera mucho ms complejo.
La lista binaria no tiene las mismas funcionalidades que las listas vistas anteriormente. No se pueden agregar elementos ni emplear el formato de anexin y supresin de elementos tal y como se haba visto antes. Pero se puede hacer de otra forma ms potente. Por ejemplo, la forma en la que tombamos la cabeza de la lista en una variable y el resto lo dejbamos en otra variable, se puede simular de la siguiente forma:
> <<H:1/binary,T/binary>> = <<"Hola">>. <<"Hola">> > H. <<"H">> > T. <<"ola">>
La concatenacin en el caso de las listas binarias no se realiza como con las listas normales empleando el operador ++. En este caso debe realizarse de la siguiente forma:
> A = <<"Hola ">>. <<"Hola ">> > B = <<"mundo!">>. <<"mundo!">> > C = <<A/binary, B/binary>>.
21
El lenguaje
<<"Hola mundo!">>
Para obtener el tamao de la lista binaria empleamos la funcin byte_size/1. En el caso anterior para cada una de las variables empleadas:
> byte_size(A). 5 > byte_size(B). 6 > byte_size(C). 11
Esta sintaxis es un poco ms elaborada que la de las listas, pero se debe a que nos adentramos en la verdadera potencia que tienen las listas binarias: el manejo de bits.
22
El lenguaje
En este ejemplo se ve que la mquina de la prueba es de tipo big u ordenacin Intel. Signo: se indica si el nmero indicado se almacenar en formato con signo o sin l, es decir, signed o unsigned, respectivamente. Tipo: es el tipo con el que se almacena el dato en memoria. Segn el tipo el tamao es relevante para indicar precisin o nmero de bits, por ejemplo. Los tipos disponibles son: integer, float y binary. Unidad: este es el valor de la unidad, por el que multiplicar el tamao. En caso de enteros y coma flotante el valor por defecto es 1, y en caso de binario es 8. Por lo tanto: Tamao x Unidad = Nmero de bits; por ejemplo, si la unidad es 8 y el tamao es 2, los bits que ocupa el elemento son 16 bits. Si quisiramos almacenar tres datos de color rojo, verde y azul en 16 bits, tomando para cada uno de ellos 5, 5 y 6 bits respectivamente, tendramos que la particin de los bits se podra hacer de forma algo dificultosa. Con este manejo de bits, componer la cadena de 16 bits (2 bytes) correspondiente, por ejemplo, a los valores 20, 0 y 6, sera as:
> <<20:5, 0:5, 60:6>>. <<"<">>
Nota
Para obtener el tamao de la lista binaria en bits podemos emplear la funcin bit_size/1 que nos retornar el tamao de la lista binaria:
> bit_size(<<"Hola mundo!"). 88
1.5. Tuplas
Las tuplas son tipos de datos organizativos en Erlang. Se pueden crear listas de tuplas para conformar conjuntos de datos homogneos de elementos individuales heterogneos. Las tuplas, a diferencia de las listas, no pueden incrementar ni decrementar su tamao salvo por la redefinicin completa de su estructura. Se emplean para agrupar datos con un propsito especfico.
23
El lenguaje
Por ejemplo, imagina que tenemos un directorio con unos cuantos ficheros. Queremos almacenar esta informacin para poder tratarla y sabemos que va a ser: ruta, nombre, tamao y fecha de creacin. Esta informacin se podra almacenar en forma de tupla de la siguiente forma:
{ "/home/yo", "texto.txt", 120, {{2011, 11, 20}, {0, 0, 0}} }.
Las llaves indican el inicio y fin de la definicin de la tupla, y los elementos separados por comas conforman su contenido.
Nota
En el ejemplo se puede ver que la fecha y hora se ha introducido de una forma un tanto peculiar. En Erlang, las funciones de los mdulos de su librera estndar, trabajan con este formato, y si se emplea, es ms fcil tratar y trabajar con fechas. Por ejemplo, si ejecutsemos:
> {date(), time()}. {{2011,12,6},{22,5,17}}
Este tipo de dato tambin se emplea para emular los arrays asociativos (o hash). Estos arrays almacenan informacin de forma que sea posible rescatarla mediante el texto o identificador especfico que se us para almacenarla. Se usa en aquellos casos en que es ms fcil que acceder al elemento por un identificador conocido que por un ndice que podra ser desconocido.
Ahora supongamos que de esta lista, que se ha cargado desde algn fichero o mediante cualquier otro mtodo, queremos consultar si debemos de realizar o no la depuracin del sistema, es decir, mostrar mensajes de log si la propiedad debug es igual a true:
> proplists:get_value(debug, A).
24
El lenguaje
true
Como es muy posible que no se sepan las claves que existen en un determinado momento dentro de la lista existen las funciones is_defined, o get_keys para poder obtener una lista de claves de la lista. Un ejemplo de posible uso como tabla hash sera:
> Meses = [ {enero, 31}, {febrero, 28}, {marzo, 31}, {abril, 30}, {mayo, 31}, {junio, 30}, {julio, 31}, {agosto, 31}, {septiembre, 30}, {octubre, 31}, {noviembre, 30}, {diciembre, 31} ]. > proplists:get_value(enero, Meses). 31 > proplists:get_value(junio, Meses). 30
El empleo de las listas de propiedades de esta forma nos facilita el acceso a los datos que sabemos que existen dentro de una coleccin (o lista) y extraer nicamente los que queramos obtener.
Nota
El mdulo de proplists contiene muchas ms funciones tiles para tratar este tipo de coleccin de datos de forma fcil. No es mala idea dar un repaso al mismo para ver el partido que podemos sacarle en nuestros programas.
1.6. Registros
Los registros son un tipo especfico de tupla que facilita el acceso a los datos individuales dentro de la misma mediante un nombre y una sintaxis de acceso mucho ms cmoda para el programador. Internamente para Erlang, los registros realmente no existen. A nivel de preprocesador son intercambiados por tuplas. Esto quiere decir que los registros en s son una simplificacin a nivel de uso de las tuplas. Como los registros se emplean a nivel de preprocesador, en la consola slo podemos definir registros empleando un comando especfico de consola. Adems, podemos cargar los registros existentes en un fichero y emplearlos desde la propia consola para definir datos o para emplear los comandos propios de manejo de datos con registros. La definicin de registros desde la consola se realiza de la siguiente forma:
> rd(agenda, {nombre, apellidos, telefono}).
25
El lenguaje
Nota
Los ficheros de cdigo de Erlang normalmente tiene la extensin erl, sin embargo, cuando se trata de cdigos de tipo cabecera, estos ficheros mantienen una extensin a medio camino entre los de cabecera de C (que tienen la extensin .h) y los de cdigo normales de Erlang. Su extensin es: hrl. En estos ficheros se introducirn normalmente definiciones y registros.
Veamos con una pequea prueba que si creamos una tupla A Erlang la reconoce como tupla de cuatro elementos. Si cargamos despus el archivo registros.hrl cuyo contenido es la definicin del registro agenda el tratamiento de la tupla se modifica automticamente y ya podemos emplear la notacin para registros de los ejemplos subsiguientes:
> A = {agenda, "Manuel", "Rubio", 666666666}. {agenda,"Manuel","Rubio",666666666} > rr("registros.hrl"). [agenda] > A. #agenda{nombre = "Manuel",apellidos = "Rubio", telefono = 666666666}
Erlang reconoce como primer dato de la tupla el nombre del registro y como cuenta con el mismo nmero de elementos, si no tenemos en cuenta el identificador, la considera automticamente como un registro. Tambin se pueden seguir empleando las funciones y elementos tpicos de la tupla ya que a todos los efectos sigue sindolo.
Nota
Para obtener la posicin dentro de la tupla de un campo, basta con escribirlo de la siguiente forma:
#agenda.nombre
Esto nos retornar la posicin relativa definida como nombre con respecto a la tupla que contiene el registro de tipo agenda.
Para tratar los datos de un registro, podemos realizar cualquiera de las siguientes acciones:
> A#agenda.nombre.
26
El lenguaje
"Manuel" > A#agenda.telefono. 666666666 > A#agenda{telefono=911232323}. #agenda{nombre = "Manuel",apellidos = "Rubio", telefono = 911232323} > #agenda{nombre="Juan Antonio",apellidos="Rubio"}. #agenda{nombre = "Juan Antonio",apellidos = "Rubio", telefono = undefined}
Recordemos siempre que la asignacin sigue siendo nica. Para acceder al contenido de un dato de un campo del registro, accederemos indicando que es un registro (dato#registro, A#agenda en el ejemplo) y despus agregaremos un punto y el nombre del campo al que queremos acceder. Para modificar los datos de un registro existente en lugar del punto emplearemos las llaves. Dentro de las llaves estableceremos tantas igualdades clave=valor como necesitemos (separadas por comas), tal y como se ve en el ejemplo anterior. Para obtener en un momento dado informacin sobre los registros, podemos emplear la funcin record_info. Esta funcin tiene dos parmetros, el primero es un tomo que puede contener fields si queremos que retorne una lista de tomos con el nombre de cada campo; o size, para retornar el nmero de campos que tiene la tupla donde se almacena el registro (includo el identificativo, en nuestros ejemplos agenda).
Importante
Como se ha dicho anteriormente, los registros son entidades que trabajan a nivel de lenguaje pero Erlang no los contempla en tiempo de ejecucin. Esto quiere decir que el preprocesador trabaja para convertir cada instruccin concerniente a registros para que sean relativas a tuplas y por tanto la funcin record_info no se puede emplear con variables. Algo como lo siguiente:
> A = agenda, record_info(fields, A).
Como los registros son internamente tuplas cada campo puede contener a su vez cualquier otro tipo de dato, no slo tomos, cadenas de texto o nmeros, sino tambin otros registros, tuplas o listas. Con ello, esta estructura nos propone un sistema organizativo interesante para poder acceder directamente al dato que necesitemos en un momento dado facilitando la labor del programador enormemente.
27
El lenguaje
Nota
Para los que hayan programado con lenguajes tipo C, Java, PHP, ... esta funcin es equivalente y muy parecida a printf, es decir, la funcin se basa en una cadena de texto con un formato especfico (agregando parmetros) que sern sustituidos por los valores que se indiquen en los parmetros siguientes.
Por ejemplo, si quieres mostrar una cadena de texto por pantalla, podemos escribir lo siguiente:
> io:format("Hola mundo!"). Hola mundo!ok
Esto sale as porque el retorno de la funcin es ok, por lo que se imprime la cadena de texto y seguidamente el retorno de la funcin (el retorno de funcin se imprime siempre en consola). Para hacer un retorno de carro, debemos de insertar un caracter especial. A diferencia de otros lenguajes donde se usan los caracteres especiales, Erlang no usa la barra invertida, sino que emplea la virgulilla (~), y tras este smbolo, los caracteres se interpretan de forma especial. Tenemos: ~ Imprime el smbolo de la virgulilla. c Representa un carcter que ser reemplazado por el valor correspondiente pasado en la lista como segundo parmetro. Antes de la letra c se pueden agregar un par de nmeros separados por un punto. El primer nmero indica el tamao del campo y la justificacin a izquierda o derecha segn el signo positivo o negativo del nmero. El segundo nmero indica las veces que se repetir el caracter. Por ejemplo:
28
El lenguaje
e/f/g Se encargan de presentar nmeros en coma flotante. El formato de e es cientfico (X.Ye+Z) mientras que f lo presenta en formato con coma fija. El formato g es una mezcla ya que presenta el formato cientfico si el nmero se sale del rango [0.1,10000.0], y en caso contrario presenta el formato como si fuese e. Los nmeros que se pueden anteponer a cada letra indican, el tamao que se quiere representar y justificacin (como se vi antes). Tras el punto la precisin. Unos ejemplos:
> io:format("[~7.2e,~7.2f,~7.4g]", [10.1,10.1,10.1]). [ 1.0e+1, 10.10, 10.10]ok > Args = [10000.67, 10123.23, 1220.32], > io:format("~11.7e | ~11.3f | ~11.7g ", Args). 1.000067e+4 | 10123.230 | 1220.320 ok
s Imprime una cadena de caracteres. Similar a c, pero el significado del segundo nmero en este caso es la cantidad de caracteres de la lista que se mostrar. Veamos algunos ejemplos:
> Hola = "Hola mundo!", > io:format("[~s,~-7s,~-7.5s]", [Hola, Hola, Hola]). [Hola mundo!,Hola mu,Hola ]ok
w/W Imprime cualquier dato con su sintaxis estandar. Se usa sobretodo para poder imprimir tuplas, pero imprime igualmente listas, nmeros, tomos, etc. La nica salvedad, es que una cadena de caracteres ser considerada como una lista. Los nmeros de anteposicin se emplean de la misma forma que en s. Un ejemplo:
> Data = [{hola,mundo},10,"hola",mundo], > io:format("[~w,~w,~w,~w]~n", Data). [{hola,mundo},10,[104,111,108,97],mundo] ok
La versin de W es similar a la anterior aunque toma dos parmetros de la lista de parmetros. El primero es el dato que se va a imprimir, el segundo es la profundidad. Si imprimimos una lista con muchos elementos, podemos mostrar nicamente un nmero determinado de ellos. A partir de ese nmero agrega puntos suspensivos. Un ejemplo:
29
El lenguaje
p/P Es igual que w, pero intenta detectar si una lista es una cadena de caracteres para imprimirla como tal. Si la impresin es demasiado grande, la parte en varias lneas. La versin en mayscula, tambin es igual a su homnimo W, aceptando un parmetro extra para profundidad. b/B/x/X/+/# Imprimen nmeros segn la base indicada. Los nmeros anteriores a cada letra (o smbolo) indican, el primero la magnitud y justificacin de la representacin y el segundo la base en la que se expresar el nmero. La diferencia entre ellos es que B imprime slo la representacin numrica. Con X se puede emplear un prefijo que se toma del siguiente parmetro que haya en la lista de parmetros, consecutivo al valor a representar. El smobolo de almohadilla (#) siempre antepone la base en formato Erlang: 10#20 (decimal), 8#65 (octal), 16#1A (hexadecimal). La diferencia entre las maysculas y minsculas es precisamente esa, la representacin de las letras de las bases mayores a 10 en maysculas o minsculas. Un ejemplo:
> io:format("[~.2b,~.16x,~.16#]", [21,21,"0x",21]). [10101,0x15,16#15]ok
i Ignora el parmetro que toque emplear. Es til si el formato de los parmetros que se pasa es siempre el mismo y en un formato especfico se desea ignorar uno concreto. n Retorno de carro, hace un salto de lnea, de modo que se pueda separar por lneas diferentes lo que se desee imprimir por pantalla.
Nota
Existe tambin el mdulo io_lib que dispone tambin de la funcin format. La nica diferencia que presenta, es que en lugar de presentar por pantalla la cadena resultante, la retorna como cadena de caracteres.
30
El lenguaje
3. Fechas y Horas
El manejo de fechas y horas en Erlang no se realiza con un tipo estndar, sino que se establece como un trmino encerrado en una tupla. Una fecha tiene la siguiente forma de tupla:
{2012,5,22}
Es una tupla compuesta por tres campos enteros destinados al ao, mes y da, en ese orden. La funcin interna date/0 retorna este formato, pero hay ms funciones de tratamiento de fecha que emplean este formato. El tiempo tambin se maneja en una tupla de tres elementos en la que se pueden diferenciar en este orden: hora, minutos y segundos. Un ejemplo sera el siguiente:
{22,10,5}
Una fecha y hora completa se representa a travs de otra tupla que contiene en su interior las tuplas mencionadas antes, separadas en dos elementos diferenciados, es decir, un formato como el siguiente:
> erlang:localtime(). {{2012,5,22},{22,10,5}}
Para obtener la fecha y hora en la zona horaria local podemos emplear tambin estas otras funciones dentro de una tupla de dos elementos: {date(), time()} Hay otras funciones como now/0, que retornan la fecha y hora actuales 2 en formato POSIX , en una tupla {MegaSeconds, Seconds, MicroSeconds}, lo que quiere decir que el clculo de la hora en un slo entero sera as:
> {M,S,_} = now(), M*1000000+S. 1337717405
Por ltimo, indicar que las fechas tambin pueden ser convertidas o empleadas en formato UTC (o GMT). Podemos convertir una fecha a formato UTC (erlang:localtime_to_universaltime/1) o viceversa (erlang:universaltime_to_localtime/1).
El formato de POSIX para fecha y hora consiste en un nmero entero que corresponde al nmero de segundos transcurrido desde el 1 de enero de 1970 hasta la fecha que se indique.
31
El lenguaje
Nota
El mdulo calendar provee una serie de funciones que permiten averiguar si el ao introducido es bisiesto (is_leap_year/1), el da de la semana de una fecha concreta (iso_week_number/0 e iso_week_number/1), el ltimo da del mes (last_day_of_the_month/2) y ms an. Este mdulo, adems, tiene la capacidad de trabajar con segundos gregorianos en lugar de POSIX. El nmero obtenido en segundos 3 (para representacin interna) es contado desde el ao cero , en lugar de 1970. Esto da la posibilidad de dar fechas anteriores a 1970.
La toma de segundos siempre es en formato UTC (o GMT), por lo que las fechas que se proporcionen para la conversin a segundos, sern tomadas como en hora local y convertidas a UTC antes de su conversin a segundos.
32
1. Expresiones
Las expresiones son la conjuncin de smbolos con datos para conformar una sentencia vlida para el lenguaje con significado para el compilador, de modo que pueda ofrecer, en tiempo de ejecucin, una representacin a nivel de cdigo mquina del resultado que se pretende obtener. Las expresiones pueden ser de tipo aritmtico o lgico. Las aritmticas buscan un valor a travs de operaciones matemticas simples o complejas. De un conjunto de datos dados con las operaciones indicadas y el orden representado por la expresin se obtiene un resultado. En las lgicas se busca una conclusin lgica (o binaria) a la conjuncin de los predicados expuestos.
33
Se puede hacer uso de los parntesis para establecer una relacin de precedencia de operadores para, por ejemplo, anteponer una suma a una multiplicacin. Tambin se pueden realizar operaciones encadenadas, por ejemplo multiplicando ms de dos operandos. Ejemplos de todo esto:
> 2 * 3 + 1. 7 > 2 * (3 + 1). 8 > 3 * 3 * 3. 27
Estas herramientas nos facilitan operar de forma binaria con los nmeros. Tambin podemos encontrarnos con que queremos almacenar el resultado, o emplear el valor lgico de una serie de comparaciones. Para ello ya no operamos de forma binaria, sino que obtenemos resultados binarios nicos como true o false. Podramos hacer:
> C1 = 2 > 1. true > C2 = 1 > 2. false > C1 and C2. false > C1 or C2. true > C3 = 3 =:= (1 + 2). true > C1 and (C2 or C3). true
Podemos construir todas las expresiones lgicas que queramos de modo que a nivel de comparacin podamos obtener un resultado
34
lgico (verdadero o falso). En la siguiente seccin se mencionan todos los operadores de comparacin que se pueden emplear para realizar comparaciones entre cadenas, nmeros, tuplas, listas y/o registros.
Nota
Adems de los operadores and y or, en Erlang existen otros como andalso y orelse. El resultado a nivel de clculo es el mismo. Lo nico que vara es que los primeros realizan una comprobacin absoluta de los valores pasados, evaluando y comparando todos los valores, mientras que los presentados recientemente, realizan una comprobacin vaga. Esto quiere decir que se evala la primera parte de la expresin y, en caso de andalso (por ejemplo), si es falsa, ya se sabe que el resultado general ser falso, por lo que no se comprueba la segunda parte, retornando inmediatamente el valor false. Son tiles si la comprobacin se debe hacer consultado una funcin que tiene un coste de comprobacin asociado, ya que muchas veces es mejor ahorrarse esas ejecuciones. Lo mismo se aplica a una comprobacin que pueda fallar por lo que necesitamos otra anterior que descarta la segunda. Por ejemplo:
is_list(List) andalso length(List)
Si List no fuese una lista, la ejecucin de length/1 fallara. Al emplear andalso esto no sucede, ya que slo se comprueba la primera parte, y al obtener false finaliza las comprobaciones.
35
Descripcin Y lgico con comprobacin vaga O lgico con comprobacin vaga Asignacin y Paso de mensaje Captura de errores
2. Estructuras de Control
A diferencia de los lenguajes imperativos en Erlang slo hay dos estructuras de control: if y case; aunque se puedan parecer a las estructuras que existen en otros lenguajes, difieren. Estas estructuras se basan en la concordancia de sus expresiones. Ambas tienen que realizar una concordancia positiva con una expresin y ejecutar un cdigo que retorne un valor. Como el que encajen los valores es tan importante para estas estructuras, y para la mayora de estructuras en ejecucin dentro de la programacin de Erlang, en general, dedicaremos una parte a estudiar lo que llamaremos a partir de ahora como concordancia y seguidamente veremos las estructuras donde se aplica.
2.1. Concordancia
En este apartado revisaremos un aspecto bastante importante en lo que respecta a la programacin en Erlang y que conviene tener interiorizado, lo que facilitar mucho la programacin en este lenguaje. Me refiero a la concordancia (en ingls match). Podramos definir esta expresin como la cualidad de una estructura de datos de asemejarse a otra, incluso aunque haya que aplicar asignacin para ello. Si tenemos un conjunto de datos, por ejemplo una lista, podemos hacer un simple concordancia haciendo:
[1,2,3] = [1,2,3]
Si realizamos esta asignacin, veremos que nos da como resultado [1,2,3], es decir, se acepta que el valor de la izquierda es igual al de la derecha (como en matemticas: es un aserto vlido). Ahora bien, si tenemos el dato de la derecha que lo desconocemos, como habamos visto en la listas, podemos hacer:
[A,B,C] = [1,2,3]
36
Esto nos dar como resultado la asociacin a A, B y C de los valores 1, 2 y 3, respectivamente, por lo que retornar como en el caso anterior, [1,2,3]. En la seccin de listas comentamos ms formas de hacer concordancia a travs de la agregacin de conjunto (++) o con la lista en formato cabezacola ([H|T]). Con respecto a las tuplas, esto no es aplicable, ya que la tupla tiene valores fijos, pero podemos ignorar los que no nos interesen de la siguiente forma:
{A,_,C} = {1,2,3}
Con el smbolo de subrayado (o guin bajo "_"), le decimos al sistema que en ese espacio debe de haber un dato (del tipo que sea: lista, tupla, tomo, nmero o registro), pero que no nos interesa.
En este ejemplo podemos ver cmo, si la estructura que se indica en case casa con cualquiera que se suceda en las subsiguientes lneas, se ejecuta un bloque concreto, retornando el resultado de la ejecucin de dicho bloque (en este ejemplo slo un valor). Si no se encontrase ningn valor que casara, la estructura no podra retornar nada y dara un error. Es aconsejable acabar con un subrayado (_) que casa con todo y tomarlo como valor por defecto, a menos que se quiera expresamente que falle en caso de que no se contenga un valor apropiado. Podemos ver otro ejemplo ms complejo como el siguiente:
> Resultado = case Fecha of
37
{D,M,A} -> integer_to_list(A) ++ "-" ++ integer_to_list(M) ++ "-" ++ integer_to_list(D); <<Dia:2/binary,"/",Mes:2/binary,"/",Agno:4/binary>> -> binary_to_list(Agno) ++ "-" ++ binary_to_list(Mes) ++ "-" ++ binary_to_list(Dia); _ -> "" end.
Si la variable Fecha la igualamos al retorno de la funcin date() el sistema entender que casa con el primer bloque, ya que es una tupla de 3 elementos, convertir cada dato y lo concatenar con los guiones para retornarlo en modo texto con formato A-M-D. Si lo que enviamos es un texto en una lista binaria separado por barras inclinadas (/), tomar cada parte y lo representar anlogamente. En caso de no casar con ninguno de los anteriores, retorna una cadena vaca. La estructura case puede agregar condicionales a cada opcin para la 1 concordancia. Esto es lo que se conoce como guardas . Estas expresiones se pueden agregar empleando conexiones como: andalso o "," y orelse o ";". Estas guardas se agregan tras cada opcin con la palabra clave when, tal y como se ve en el siguiente ejemplo:
> Resultado = case Fecha of {D,M,A} when is_integer(D), is_integer(M), is_integer(A) -> integer_to_list(A) ++ "-" ++ integer_to_list(M) ++ "-" ++ integer_to_list(D); <<Dia:2/binary,"/",Mes:2/binary,"/",Agno:4/binary>> when is_binary(Fecha) -> binary_to_list(Agno) ++ "-" ++ binary_to_list(Mes) ++ "-" ++ binary_to_list(Dia); _ -> "" end.
Con esto nos aseguramos de que los valores que se parsearn dentro de cada bloque son del tipo que se esperan, y que algo como una tupla que contenga listas de caracteres no haga fallar el primer bloque de opcin. Para las guardas se pueden emplear tanto "," como and, o andalso, en caso de que se quiera el comportamiento del y lgico; o ";", or o orelse, para conseguir el comportamiento del o inclusivo lgico. La diferencia existente entre las tres formas es que el agregado also o else hace que sea una comprobacin vaga pudiendo finalizar antes de
1
Esta expresin inglesa se ha traducido en sitios como aprendiendo erlang como guardas.
38
evaluar todos los predicados. Los signos de puntuacin se comportan de la misma forma en este caso. La diferencia entre los signos "," y ";" con andalso y orelse es que los signos capturan excepciones. Es decir mediante el uso de los signos de puntuacin se ignorarn los fallos que puedan suceder en la evaluacin, continuando con la evaluacin de lo siguiente. Para aclarar mejor las diferencias veamos tres ejemplos de cdigo similares pero que funcionan de forma bastante diferente:
> case a of > _ when (a+1)=:=a or b=:=b -> ok; > _ -> fail > end. * 1: syntax error before: '=:=' > case a of > _ when (a+1)=:=a orelse b=:=b -> ok; > _ -> fail > end. fail > case a of > _ when (a+1)=:=a ; b=:=b -> ok; > _ -> fail > end. ok
El uso de or nos da un error de cdigo directamente, ya que estamos sumando 1 a un tomo llamado a y eso da bad argument in arithmetic expression. Mediante el uso de orelse no nos da error, pero ignora toda esa comprobacin por ser errnea, pasando a comprobar el siguiente bloque y devolviendo fail. Por ltimo, con el signo ";", en lugar de tomar ese resultado como no vlido e invalidar toda la comprobacin como el caso anterior, slo da como invlida la primera parte y pasa a comprobar el siguiente predicado, considerando que la primera parte retorna false.
2.3. Estructura if
Otra de las estructuras que se puede emplear con Erlang es if. Esta estructura guarda cierta similitud con las que se emplean en los lenguajes imperativos, salvo porque debe existir una opcin de cdigo que sea ejecutable en caso de que la clusula previa se cumpla; adems y en todo caso que se debe retornar siempre un valor. Si nos fijamos bien esta estructura podra tomarse como una simplificacin de la estructura case anterior. La nica diferencia radica en la eliminacin de los bloques de concordancia. Es decir, slo emplea las guardas. Por ejemplo, la siguiente estructura if devuelve el caso1 si el da de hoy est entre los valores 1 y 10, y si es sobre 11 y 20, caso2. En caso de
39
ejecutarse la funcin mostrada con los valores mayores o iguales a 21 dara un error:
> {A,M,D} = date(). {2012,4,25} > Caso = if > (D >= 1) and (D =< 10) -> caso1; > (D >= 11) and (D =< 20) -> caso2 > end. ** exception error: no true branch found when evaluating an if expression
Este error es debido a que esta estructura, al igual que el resto de estructuras existentes en Erlang, debe de retornar un valor y en caso de no poder ejecutar ningn bloque de cdigo para resolver la funcin o valor que debe devolver, origina el fallo.
Importante
En otros lenguajes, el operador de mayor que (>) y menor que (<) se sita siempre antes del signo igual, mientras que, como se vio en la tabla de precedencia de operadores, segn si es uno u otro, se coloca de modo que apunte siempre hacia el smbolo de igualdad. En la misma tabla de precedencia de operadores se puede ver que and y or tienen ms prioridad que las comparaciones, por lo que, en caso de que se usen stos y no el punto y coma (;) o la coma (,) u orelse o andalso, es necesario encerrar la comparacin entre parntesis.
Para que el sistema no nos falle cuando introduzcamos fechas a partir del da 21, vamos a definir una accin por defecto:
> Caso = if > D >= 1 andalso D =< 10 -> caso1; > D >= 11 andalso D =< 20 -> caso2; > true -> unknown > end.
A diferencia de la estructura case, el valor de comodn no se hace sobre una variable que pueda contener cualquier valor (como en el caso de subrayado, por ejemplo), sino se emplea la palabra reservada true por tratarse de predicados lgicos.
40
indicando: de donde procede esta informacin, cul queremos que sea su formato de salida y las condiciones que debe de cumplir nos proporciona dicha informacin al instante. Por ejemplo, si queremos sacar de una lista slo los nmeros pares, sera tan sencillo como:
> [ X || X <- [1,2,3,4,5], X rem 2 =:= 0 ]. [2,4]
Si expresamos esto mismo en lenguaje natural sera algo as como: [ Dame X || Donde X es un elemento de la lista <- [1,2,3,4,5], tal que la condicin X rem 2 =:= 0 se cumpla. Las listas de comprensin tienen tres partes que se enmarcan dentro de los corchetes. La primera es la proyeccin de los elementos, es decir, indica la forma en la que se presentarn los datos o en la que queremos que se configure la salida de la ejecucin de la lista de comprensin. La segunda es la seleccin de los datos. Esta parte est separada de la primera por dos pipes (||) y tiene una flecha de derecha a izquierda que indica a la derecha el origen de los datos y a la izquierda el patrn o forma de los datos. La tercera parte, separada por una coma de la anterior, son las condiciones de la seleccin. Las condiciones que debe de cumplir cada elemento de la lista, para ser seleccionado. En el caso del ejemplo se indic que el valor de X deba de ser par (que su remanente fuese cero en una divisin por dos).
Nota
Las listas de comprensin son uno de los elementos ms importantes del lenguaje, por lo que conviene que se tenga muy presente su forma, la utilidad que tienen con respecto a la seleccin y proyeccin de informacin y realizar pruebas hasta comprender su funcionamiento completa y correctamente. Un truco bastante til que yo empleo es compararlo con una sentencia SELECT de SQL, ya que tiene la parte de la proyeccin (inmediatamente despus de SELECT), la parte de la seleccin (la parte del FROM) y las condiciones de la seleccin para cada tupla (la parte del WHERE).
Un ejemplo ms completo, teniendo listas de listas, pero siendo una matriz fija de 2xN, por ejemplo, podemos realizar la siguiente seleccin:
> A = [[1,1],[2,2],[3,3],[4,4],[5,5],[6,6]]. [[1,1],[2,2],[3,3],[4,4],[5,5],[6,6]]
41
La lista resultado nos muestra, dentro de una sublista de dos elementos a los que asociamos como (Y,X), el hecho de que el elemento Y deba de ser par y el elemento X mayor o igual a 4. Por lo que, en esta definicin, concuerdan los nmeros 4 y 6.
3. Excepciones
Erlang es tolerante a fallos. Esto le viene dado por el empleo de procesos en lugar de hilos. Si un proceso muere y deja su estado de memoria corrupto no afectar a otros procesos, ya que ni siquiera comparten memoria (cada proceso tiene la suya propia y es otra de las propiedades de Erlang el nada compartido o share nothing en ingls), ni la ejecucin de uno est condicionada o afecta a otros procesos. El tema de los procesos lo veremos en el siguiente captulo de forma ms extensa. Ahora vamos a centrarnos en las excepciones, porque, qu suecede cuando un proceso encuentra un fallo o una situacin inesperada por el programador? Normalmente se dispara una excepcin que hace que el proceso muera. En el siguiente captulo veremos que eso en muchos casos es asumible e incluso deseable. Pero tambin hay casos en los que, si el cdigo maneja recursos que hay que tratar de llevar a una situacin segura antes de que suceda lo inevitable, es preferible intentar de realizar algn tratamiento para esa excepcin.
La ejecucin de la primera expresin nos lleva a una excepcin que propocara la finalizacin de ejecucin del proceso, mientras que anteponiendo catch a la misma expresin, Erlang convierte esa excepcin en un tipo de dato que se podra procesar a travs de una estructura de control.
42
En este caso, el sistema no produce un error, sino que retorna el casoError, que debe de ser manejado por el cdigo que toma el retorno de esta instruccin.
Importante
En este caso es una mala idea haber capturado la excepcin ya que tapa un error de cdigo que hemos provocado y que, gracias a catch, hace que consideremos el cdigo como correcto, cuando no es as.
Como el cdigo es errneo y 5 no es igual a 4, el sistema se detiene en ese punto. Esto nos garantiza que, si el cdigo es crtico y no debe de contener errores, en unas pruebas podra aparecer el error y ser solucionado. Adems de esta tcnica, podemos lanzar excepciones con mensajes de error concretos, por si quisiramos a otro nivel capturarlos para procesarlos. Estos se lanzaran a travs de throw. Podemos verlo ms claro a travs de un ejemplo:
> throw({fallo, "Esto ha fallado"}). ** exception throw: {fallo,"Esto ha fallado"}
43
En caso de que quisiramos capturarlo con catch, el sistema trata este lanzamiento de excepcin como un error real provocado por el usuario, por lo que se podra capturar como cualquier otro error provocado por el sistema.
En la parte de catch se declaran tres partes diferenciadas. Estas se detallan con su clase, que puede ser cualquiera de las tres: throw, exit o error. A continuacin y despus de los dos puntos (:) est la variable que contendr el mensaje en s del error para poder emplearlo dentro del bloque de cdigo de recuperacin. Esta sentencia presenta tambin una zona en la que poder ejecutar acciones que se lleven a cabo tanto si el cdigo falla como si no. Esta seccin recibe el nombre de after, y es un bloque de cdigo que se agrega tras catch. Por ejemplo, si queremos imprimir por pantalla un saludo falle o no el cdigo:
> try > a=1 > catch > error:Error -> Error > after > io:format("Adios~n") > end. Adios {badmatch,1}
El cdigo se ejecuta de modo que, como after est dentro de la estructura, hasta que esa seccin no termina (en este caso imprimir Adios por pantalla) la estructura no retorna el valor correspondiente a su ejecucin (la excepcin a travs de la rama error:Error).
44
Nota
Podramos profundizar ms en estas estructuras, pero lo dejo en este punto porque me gusta ms la filosofa de Erlang: let it crash (deja que falle); que indica que el sistema debe de poder fallar para volver a iniciar su ejecucin de forma normal, ya que mantenerse en ejecucin tras un fallo podra provocar una situacin imprevista que, adems, se prolongase, con lo que dificultara an ms la deteccin del fallo.
case_clause Prcticamente igual la anterior. Este se dispara cuando no hay concordancia con ningn bloque (y sus guardas, en caso de que tuviese), dentro de la clusula case.
> case hola of adios -> "" end. ** exception error: no case clause matching hola
if_clause Al igual que el resto de *_clause, este error se dispara cuando no hay ninguna guarda del if aplicable. El sistema indicar que no hay rama true disponible, ya que es una prctica habitual el disponer de la misma.
> if false -> "" end. ** exception error: no true branch found when eval...
badmatch Suelen suceder cuando falla la concordancia (matching), ya sea al intentar asignar una estructura de datos sobre otra que no tiene la misma forma o cuando se intenta hacer una asignacin sobre una variable que ya tiene un valor.
45
> A=1, A=2. ** exception error: no match of right hand side value 2
badarg Se suele disparar cuando llamamos a una funcin con argumentos errneos. A diferencia de las ya vistas esta excepcin es introducida como una validacin de argumentos por el programador fuera de las guardas, por lo que para emplearla, debemos de crear un bloque en nuestras funciones de validacin de argumentos que, en caso de no ser correctos, la lancen. Un ejemplo de funcin que dispone de esto:
> io:format({hola}). ** exception error: bad argument
undef Lanzada cuando se llama a una funcin que no est definida (no existe), ya sea por su nmero de parmetros o por su nombre dentro del mdulo:
> lists:no_existe(). ** exception error: undefined function lists:no_existe/0
badarith Esta excepcin es para errores matemticos (aritmticos). Sucede cuando se intenta realizar una operacin con valores incorrectos (como una suma de un nmero con una lista) o divisiones por cero. Un ejemplo:
> 27 / 0. ** exception error: bad argument in an arithmetic expr...
badfun Sucede cuando se intenta emplear una variable que no contiene una funcin. Un ejemplo:
> A = hola, A(12). ** exception error: bad function hola
badarity Es un caso especfico de badfun, en este caso el error es debido a que a la funcin que contiene la variable, se le pasa un nmero de argumentos que no puede manejar, porque son ms o menos de los que soporta. Un ejemplo:
> A = fun(_,_) -> ok end, A(uno).
46
system_limit Se alcanz el lmite del sistema. Esto puede pasar cuando: tenemos demasiados procesos limitados por el parmetro de procesos mximos (se puede ampliar), o demasiados argumentos en una funcin, tomos demasiado grandes o demasiados tomos, demasiados nodos conectados, etc. Para una mejor optimizacin del sistema y entendimiento del mismo podemos leer la Gua de 2 Eficiencia de Erlang (en ingls) .
Importante
Hay que tener especial cuidado con los errores de system_limit. Son lo suficientemente graves como para parar todo el sistema (la mquina virtual de Erlang al completo).
Donde Error puede tomar cualquiera de los valores indicados anteriormente (bararg, function_clause, cause_clause, ...) y Reason tendr una descripcin de las funciones que fueron llamadas, para llegar a ese punto.
Nota
A partir de la versin de Erlang R15, en Reason se puede ver adems el nombre del fichero y nmero de lnea en el se realiz la llamada, lo cual facilita la deteccin de errores.
http://www.erlang.org/doc/efficiency_guide/advanced.html
47
El mdulo del cdigo anterior llamado mi_modulo debe guardarse en un fichero con el nombre mi_modulo.erl. El mdulo exporta, o pone a
48
disposicin de otros mdulos y de la consola la posibilidad de usar la funcin mi_funcion, cuya aridad (o nmero de parmetros) es cero. Para simplificar el tema de la exportacin en la codificacin de nuestros primeros mdulos hasta que nos acostumbremos a ella, podemos obviar el hecho de que habr funciones privadas para el mdulo y dejarlas todas abiertas. Esto se hara escribiendo esta cabecera, en lugar de la anterior:
-module(mi_modulo). -compile([export_all]).
Esta directiva le dice al compilador que exporte todas las funciones de modo que no haya que nombrarlas una a una en la sentencia export.
Nota
Una vez tengamos el fichero creado, compilarlo es tan sencillo como ir a una consola del sistema operativo y ejecutar:
erlc mi_modulo.erl
Esto genera un fichero mi_modulo.beam que ser el que emplear la mquina virtual para acceder a las funciones creadas. Tambin es posible compilar un mdulo en la consola de Erlang, en nuestro ejemplo, escribiendo:
> c(mi_modulo)
Lo cual compilar el cdigo creando el fichero mencionado anteriormente, dejndolo disponible para su uso.
La mquina virtual de Erlang busca el fichero beam en su ruta de mdulos por defecto y luego en el directorio actual. Si lo encuentra, lo carga y busca la funcin dentro del mismo. En caso de que no encontrase la funcin retornara un fallo.
49
Importante
A diferencia de otros lenguajes donde los paquetes, mdulos o libreras se pueden encontrar de modo jerrquico, Erlang establece el nombre de sus mdulos de forma plana. Esto quiere decir que si existe un mdulo llamado mi_modulo e intentamos cargar otro mdulo con el mismo nombre, se empleara el que tuviese la fecha de compilacin ms reciente. Hay que tener cuidado con el nombre de los mdulos. Por ejemplo, si se creara un mdulo vaco de nombre erlang y se intentara cargar el sistema completo se detendra, ya que se intentaran emplear las funciones del propio sistema Erlang, esenciales para su funcionamiento, y no estaran presentes en este nuevo mdulo de fecha ms reciente.
En este caso y desde el punto de vista de la exportacin, estamos dando exclusivamente acceso a la funcin get con un parmetro, tanto a otros mdulos que importasen traductor como a la consola. Desde el punto de vista de la importacin, tenemos disponible la funcin get_value del
50
mdulo proplists de modo que no tengamos que llamarla de forma 1 fully qualified .
Nota
La importacin es una tcnica que puede hacer confuso el cdigo escrito. Se recomienda no emplearla a menos que el uso masificado de la funcin en cuestin sea ms beneficioso para la lectura del cdigo que invocarla de manera fully qualified.
3. Polimorfismo y Concordancia
Una de las particularidades de las funciones de Erlang, es que disponen de polimorfismo. Si tuvisemos que programar una funcin que tuviese algunos de sus parmetros con valores por defecto, podramos emplear el polimorfismo tal y como se da en muchos otros lenguajes imperativos, definiendo dos funciones con distinto nmero de parmetros, de la siguiente forma:
multiplica(X, Y) -> X * Y. multiplica(X, Y, Z) -> X * Y * Z.
En este caso, vemos que si la funcin es llamada con dos parmetros, se ejecutara la primera forma, ya que casa con el nmero de parmetros, y en cambio, si pasamos tres parmetros, se ejecutara la segunda forma. En Erlang sin embargo este concepto se puede completar agregando la caracterstica de la simple asignacin y la concordancia, de modo que nos permite hacer algo como lo siguiente:
area(cuadrado, Base) -> Base * Base; area(circulo, Radio) -> math:pi() * Radio * Radio. area(rectangulo, Base, Altura) -> Base * Altura; area(triangulo, Base, Altura) -> Base * Altura / 2.
Cada funcin anterior nos retorna un rea, dependiendo del nmero de argumentos pero adems del contenido del primer parmetro. Gracias a ello, podemos tener funciones con el mismo nmero de parmetros y diferente comportamiento. Como se puede observar, el
1
Fully Qualified, deriviado de su uso en los nombres DNS como FQDN, resea la llamada a una funcin empleando toda la ruta completa para poder localizarlo, es decir, empleando tambin el mdulo.
51
primer parmetro puede contener los valores: cuadrado, rectangulo, triangulo o circulo (sin acentuar, ya que son tomos). En caso de recibir, por ejemplo cubo, el sistema lanzara una excepcin al no poder satisfacer la ejecucin solicitada.
Importante
Cuando se emplea el polimorfismo, es decir la declaracin de un mismo nombre de funcin para igual nmero de parmetros pero diferente contenido, se debe de separar la definicin de una funcin de la siguiente a travs del punto y coma (;), mientras que la ltima definicin debe de llevar el punto final. Esto es as para que los bloques de funciones polimrficas de este tipo estn siempre agrupados, conformando una nica estructura ms legible.
4. Guardas
Anteriormente ya vimos las guardas en las estructuras de control case e if. Como la estructura de funcin es tan similar a las estructuras de control, tambin contempla el uso de guardas, lo que le permite realizar un polimorfismo todava ms completo. Por ejemplo, si queremos, del ejemplo anterior del clculo de reas, asegurarnos de que los datos de entrada son numricos, podramos reescribir el cdigo anterior de la siguiente forma:
area(cuadrado, Base) when is_number(Base) -> Base * Base; area(circulo, Radio) when is_number(Radio) -> math:pi() * Radio * Radio. area(rectangulo, Base, Altura) when is_number(Base), is_number(Altura) -> Base * Altura; area(triangulo, Base, Altura) when is_number(Base), is_number(Altura) -> Base * Altura / 2.
Con esto agregamos un nivel ms de validacin, asegurndonos de que las entradas de las variables sean numricas o en caso contrario que no se ejecutara esa funcin. Podramos agregar en las condiciones que la Base sea mayor de 0, al igual que la Altura y Radio, y cualesquiera otras comprobaciones ms que se nos puedieran ocurrir.
5. Clausuras
Si revisamos un momento la teora lo que ahora vamos a ver podra encajar perfectamente como clausura, lambda o funcin annima. En principio, las definiciones:
52
Se llama clausura (en ingls clousure) a una funcin junto a un entorno referenciado de variables no locales. Esto quiere decir que la funcin tiene acceso a las variables del entorno en el que es definida como si fuesen globales. Por ejemplo, si definimos una funcin calculadora dentro de otra funcin llamada factoria, si en esta ltima funcin hay definida una variable llamada contador, esta variable ser accesible tambin por calculadora. Por otro lado, tenemos el clculo lambda, inventado por Alonzo Church y Stephen Kleen en 1930, que en un entorno matemtico define lo que es una funcin para abstraer las ecuaciones en un lenguaje ms simplificado (Peter Landin se encarg de llevar esta teora a Algol 60). El caso es que la teora de funciones, subprogramas y subrutinas se basa en esta teora, pero el nombre lambda, en lenguajes imperativos ha sido otorgado a funciones annimas. Por ltimo, las funciones annimas no son ms que funciones que no se declaran con un nombre sino que son declaradas y almacenadas en una variable, de modo que la variable es empleada para hacer llamadas a otras funciones, pudiendo ser pasada como parmetro o retornada como resultado, ya que en s, es tratada como un dato. Las clausuras de Erlang se basan en todas estas premisas. Son funciones que, al definirse, pueden tomar el valor de las variables del entorno en el que son definidas (ya que las variables son de simple asignacin y toman su valor en ese momento), que cumplen con la adaptacin del clculo lambda de Church y Kleen y son annimas puesto que su definicin es como una instanciacin que se almacena en una variable y puede ser enviada como parmetro, retornada como valor y adems de esto, empleada como una funcin. Se pueden escribir estas clausuras de la siguiente forma:
> A = 2. % dato de entorno > F = fun(X) -> X * A end. #Fun<erl_eval.6.111823515> > F(5). 10
En este ejemplo a la variable F se le asigna la definicin de la clausura, introduciendo dentro de su contexto el uso de una variable del entorno en el que est siendo definida, en este caso la variable A. De este modo al ejecutar la funcin F, multiplica la variable que se le pasa como parmetro por la que tiene contenida. Podemos hacer tambin que una funcin normal, o incluso una annima, nos retorne una funcin especfica que haga una accin concreta segn los datos con los que haya sido llamada la primera:
53
Como se puede apreciar, no slo se permite generar una clausura dentro de otra, sino que la generacin de las clausuras puede tener tambin guardas. Si quisiramos agregar una clausura ms al cdigo, para truncar el valor de un nmero en coma flotante en caso de que llegase como X, podramos hacer lo siguiente:
> F = fun(X) when is_integer(X) -> fun(Y) -> X * Y end; (X) when is_float(X) -> fun(Y) -> trunc(X) * Y end end.
As conseguiremos que el tratamiento de las clausuras se tome de la misma forma, tanto si se enva un dato de tipo entero como si el dato es de tipo real (o en coma flotante).
Nota
Referenciar una funcin definida de forma normal como una funcin annima o clausura se consigue de la siguiente forma:
F = fun io:format/1.
Esta declaracin nos permitira uilizar format/1 como una clausura ms empleando directamente F. Esto viene muy bien para cuando se tienen varias funciones para trabajar de una cierta forma y se desea pasar la funcin elegida como parmetro a un cdigo donde se emplear.
Por ltimo, voy a comentar el uso de las clausuras en la evaluacin perezosa. Pongamos un ejemplo. Si en un momento dado queremos
54
trabajar con una lista de infinitos trminos, o incluso con un contenido que no queremos que est siempre presente, sino que se vaya generando a medida que se necesita, podemos realizar una clausura que haga algo como lo siguiente:
-module(infinitos). -compile([export_all]). enteros(Desde) -> fun() -> [Desde|enteros(Desde+1)] end.
Aunque hemos creado una recursividad infinita (algo parecido a un bucle infinito), gracias a la evaluacin perezosa de Erlang cada nmero se va generando a medida que vamos avanzando. Retomaremos este uso cuando tratemos el tema de la recursividad.
6. Programacin Funcional
Cuando se piensa en programacin funcional, normalmente, se piensa en las listas de comprensin y en funciones sobre listas como son map, filter o fold. Estas funciones realizan un tratamiento de datos como podra hacerlo un bucle en los lenguajes imperativos. En realidad termina siendo ms potente ya que, debido a su naturaleza, se puede paralelizar. A travs del uso de clausuras, podemos hacer que se aplique un cdigo especfico a cada elemento de una lista de elementos. Veamos la lista de funciones ms importantes de este tipo que provee Erlang: map/2 Se ejecuta la clausura pasada como parmetro, recibiendo cada elemento de la lista como parmetro y retornando un valor por cada llamada que ser almacenado y retornado por map/2 al final de la ejecucin de todos los elementos. Por ejemplo:
> L = [1,2,3,4].
55
any/2 Se evala cada elemento con la clausura pasada como parmetro, debiendo retornar sta true o false. Si alguno de los elementos retorna true, la funcin any/2 retorna tambin true. Un ejemplo:
> L = [1,2,3,4]. > lists:any(fun(X) -> > if > X > 2 -> true; > true -> false > end > end, L). true
all/2 Igual que la anterior, con la salvedad de que todos los elementos evaluados deben retornar true. En el momento en el que uno retorne false, la funcin all/2 retornara false. Un ejemplo:
> L = [1,2,3,4]. > lists:all(fun(X) -> > if > X > 2 -> true; > true -> false > end > end, L). false
foreach/2 Aplica la ejecucin de la clausura a cada elemento de la lista. En principio es igual que map/2, salvo que foreach/2 no guarda el retorno de las clausuras que ejecuta ni lo retorna. Por ejemplo:
> L = [1,2,3,4]. > lists:foreach(fun(X) -> io:format("~p~n", [X]) end, L). 1 2 3 4 ok
foldl/3 - foldr/3 Esta funcin se encarga de ejecutar la clausura pasando como parmetro el elemento de la lista y el retorno de la ejecucin anterior. Es como si encadenase la ejecucin de las clausuras, que forzosamente deben aceptar los dos parmetros. La ltima letra
56
(l o r) indica desde donde se inicia la toma de elementos de la lista. Left o izquierda sera desde la cabeza hasta la cola, y right o derecha empezara a tomar elementos por el final de la lista hasta el principio. A la funcin se le pasan tres parmetros, el primero es la clausura, el segundo el valor inicial y el tercero la lista a procesar:
> L = [1,2,3,4], > F = fun(X, Factorial) -> Factorial * X end, > lists:foldl(F, 1, L). 24
mapfoldl/3 - mapfoldr/3 Estas funciones son una combinacin de map/2 y fold/3. Encadenan los resultados de cada una de las clausuras de la anterior a la siguiente comenzando por un valor inicial, guardando el resultado de ejecucin de cada clausura. El retorno de la funcin clausura debe ser una tupla en la que el primer valor es el resultado de la parte map/2 y el segundo valor es el retorno para seguir encadenando. El retorno de ambas funciones es tambin una tupla en la que el primer elemento es una lista con todos los elementos (tal y como lo hara map/2) y el segundo valor es el resultado de la parte de fold/3. Un ejemplo:
> L = [1,2,3,4], > F = fun(X, Factorial) -> {X*2, Factorial*X} end, > lists:mapfoldl(F, 1, L). {[2,4,6,8],24}
filter/2 El filtrado toma la lista inicial y ejecuta la clausura para cada elemento. La clausura debe retornar verdadero o falso (true o false). Cada elemento que cumpla con la clausura ser agregado a la lista del resultado de filter/2. Un ejemplo:
> L = [1,2,3,4], > F = fun(X) -> if X > 2 -> true; true -> false end end, > lists:filter(F, L). [3,4]
takewhile/2 En este caso, la clausura se emplea como filtro al igual que con filter/2, pero en el momento en el que un valor retorna falso termina la ejecucin. Por ejemplo:
> L = [1,2,3,4], > F = fun(X) -> if X =< 2 -> true; true -> false end end, > lists:takewhile(F, L).
57
[1,2]
dropwhile/2 Este es el complementario de takewhile. No toma ningn elemento mientras se cumpla la condicin. En el momento que se incumple la condicin, toma todos los elementos desde ese punto hasta el final. Es decir, que toma todos los elementos que no tomara takewhile/2. Un ejemplo:
> L = [1,2,3,4], > F = fun(X) -> if X =< 2 -> true; true -> false end end, > lists:dropwhile(F, L). [3,4]
splitwidth/2 Divide la lista en dos sublistas de manera equivalente a introducir en una tupla como primer valor el resultado de takewhile/2 y como segundo valor el resultado de dropwhile/2. Un ejemplo:
> L = [1,2,3,4], > F = fun(X) -> if X =< 2 -> true; true -> false end end, > lists:splitwith(F, L). {[1,2],[3,4]}
Estas son las principales funciones que pertenecen al mdulo lists. La mayora de estas funciones ya han sido agregadas a lenguajes imperativos, al igual que las listas de comprensin, por lo que es posible que muchas de ellas sean ya conocidas para el lector. Es bueno conocer estas funciones para que cuando surja la necesidad de resolucin de un problema se pueda recurrir a ellas si es posible. Si ests interesado en saber ms acerca de estas funciones, puedes echar un vistazo al mdulo lists y as ampliar tu vocabulario en Erlang.
7. Recursividad
La recursividad define el hecho de que una funcin se pueda llamar a s misma para completar el procesamiento sobre una muestra de datos a la que se puede aplicar el mismo algoritmo de forma recurrente hasta conseguir una solucin final. La diferencia entre la recursividad y realizar un cdigo iterativo, es que las variables locales que se emplean, en el caso de la recursividad, son propias para cada ejecucin aislada del problema. El lazo comn entre cada solucin o ejecucin de la funcin, son los parmetros de entrada y los parmetros de salida, el resto se almacena en variables locales, que en la mayora de lenguajes se almacena en una pila de ejecucin.
58
Nota
Erlang implementa un sistema denominado tail recursion (o recursividad de cola), que hace que la pila de una llamada a la siguiente se libere dado que el cdigo para ejecutar en esa funcin ya no es necesario. Esto evita que se produzcan errores por desbordamiento de pila, convirtiendo el cdigo recursivo en iterativo, al menos a efectos de consumo de memoria.
En esta functin, tenemos dos casos diferenciados. El caso particular representado por la primera declaracin de funcin, porque sabemos que el factorial de cero es uno. Tambin disponemos del caso general, que seran el resto de casos para una variable X lo que se resuelven multiplicando cada valor por su anterior hasta llegar a cero. Un tipo de algoritmos que se puede implementar muy fcilmente con recursin son los de divide y vencers. Estos algoritmos se basan en la divisin del problema en subproblemas ms pequeos pero similares llegando a los casos particulares. Se resuelve cada pequeo problema de forma aislada y despus se combinan las soluciones (si es necesario), para conseguir la solucin global del problema. Las tres partes que se pueden diferenciar en este algoritmo son: separacin, recursin y combinacin. Podemos ver algunos algoritmos clsicos como los de ordenacin de listas que nos pueden ayudar a comprender mejor cmo funciona la recursividad.
59
Esto lo podemos dejar dentro de una funcin que se llame separa/1 para semantizar el cdigo y diferenciarla dentro del algoritmo. La mezcla podemos hacerla a travs de recursin tambin, de modo que, dadas dos listas ordenadas podramos definirla as:
mezcla([], L) -> L; mezcla(L, []) -> L; mezcla([H1|T1]=L1, [H2|T2]=L2) -> if H1 =< H2 -> [H1|mezcla(T1,L2)]; true -> [H2|mezcla(L1,T2)] end.
La mezcla la realizamos tomando en cada paso de los datos de cabecera de las listas, el que cumpla con la condicin indicada (el que sea menor), concatenando el elemento y llamando a la funcin con los elementos restantes. Para que este algoritmo funcione ambas listas deben de estar ordenadas, por lo que hay que ir separando elementos hasta llegar al caso particular, que ser la comparacin de un elemento con otro elemento (uno con uno). Para conseguir esto, realizamos la siguiente recursin:
ordena([]) -> []; ordena([H]) -> [H]; ordena(L) -> {L1,L2} = separa(L), mezcla(ordena(L1), ordena(L2)).
Como puedes observar, antes de llamar a la mezcla, para cada sublista, se vuelve a llamar a la funcin ordena/1, con lo que llega hasta la comparacin de un slo elemento con otro. Despus un nivel ms alto de dos con dos, tres con tres, y as hasta poder comparar la mitad de la lista con la otra mitad para acabar con la ordenacin de la lista de nmeros. Como dijimos al principio, la complejidad se presenta en la combinacin, o funcin mezcla/2, que de forma recursiva se encarga de comparar los elementos de una lista con la otra para conformar una sola en la que estn todos ordenados. El cdigo completo del algoritmo es el siguiente:
-module(mergesort). -export([ordena/1]). separa(L) -> lists:split(length(L) div 2, L).
60
mezcla([], L) -> L; mezcla(L, []) -> L; mezcla([H1|T1]=L1, [H2|T2]=L2) -> if H1 =< H2 -> [H1|mezcla(T1,L2)]; true -> [H2|mezcla(L1,T2)] end. ordena([]) -> []; ordena([H]) -> [H]; ordena(L) -> {L1,L2} = separa(L), mezcla(ordena(L1), ordena(L2)).
Hemos dejado exportada solamente la funcin ordena/1, de modo que para poder emplear el algoritmo habra que hacerlo as:
> mergesort:ordena([1,7,5,3,6,2]). [1,2,3,5,6,7]
La parte de la mezcla es trivial puesto que se recibirn listas ya ordenadas como parmetros. La mezcla consiste slo en concatenar las sublistas y retornar el resultado. La parte de la recursividad, es muy parecida a la de mergesort. Viendo el cdigo al completo:
-module(quicksort).
61
-export([ordena/1]). separa([]) -> {[], [], []}; separa([H]) -> {[H], [], []}; separa([Pivote|T]) -> Menor = [ X || X <- T, X =< Pivote ], Mayor = [ X || X <- T, X > Pivote ], {Menor, [Pivote], Mayor}. mezcla(L1, L2) -> L1 ++ L2. ordena([]) -> []; ordena([H]) -> [H]; ordena(L) -> {L1, [Pivote], L2} = separa(L), mezcla(ordena(L1) ++ [Pivote], ordena(L2)).
Se puede ver que la estrategia de divide y vencers se mantiene. Por un lado separamos la lista en dos sublistas seleccionando un pivote, retornando ambas sublistas y el pivote. Las sublistas se ordenan mediante recursin sobre cada sublista por separado. La ejecucin de este cdigo sera as:
> quicksort:ordena([1,7,5,3,6,2]). [1,2,3,5,6,7]
8. Funciones Integradas
En Erlang existen funciones que no estn escritas en Erlang, sino que el sistema las procesa a bajo nivel y forman parte de la mquina virtual como instrucciones base que se ejecutan mucho ms rpido. Estas funciones construidas en el sistema se albergan bajo el mdulo erlang. Normalmente no hace falta referirse al mdulo para emplearlas (a menos que exista ambigedad). Algunas de ellas ya las hemos visto: is_integer/1, integer_to_list/1, length/1, e incluso las operaciones matemticas, lgicas y otras. Un ejemplo:
> erlang:'+'(2, 3). 5
Estas funciones reciben el nombre de BIF (en ingls Built-In Functions). Otros ejemplos de BIFs son el clculo de MD5 (md5/1), el redondeo de nmeros (round/1) y el clculo de la fecha (date/0) o la hora (time/0).
62
Nota
Robert Virding, uno de los creadores/fundadores/inventores de 2 Erlang, coment en un artculo de su blog , lo confuso que resulta determinar qu es un BIF y qu no. Un intento de definirlo por parte de Jonas Barklund y Robert Virding disponible en la especificacin (no indica URL especfica el autor en su blog), es que un BIF fue una parte del lenguaje Erlang que no dispona de una sintaxis concreta o especial, por lo que se mostraba como una llamada a funcin normal.
http://rvirding.blogspot.com.es/2009/10/what-are-bifs.html
63
Captulo 5. Procesos
Cuando ests en un atasco de trfico con un Porsche, todo lo que puedes hacer es consumir ms combustible que el resto estando parado. La escalabilidad va de construir carreteras ms anchas, no coches ms rpidos. Steve Swartz Una de las grandes fortalezas de la plataforma de Erlang es la gestin de procesos. Los procesos en Erlang son propios de la mquina virtual y en cada plataforma tienen las mismas caractersticas y se comportan de la misma forma. En definitiva, no se emplean los mecanismos propios del sistema operativo para ello sino que es la propia mquina virtual quien provee los mecanismos para su gestin. Para comenzar analizaremos la anatoma de un proceso en Erlang para comprender para lo que es, los mecanismos de comunicacin de que dispone y sus caractersticas de monitorizacin y enlazado con otros procesos. Muchas de estas caractersticas estn presentes en los procesos nativos de sistemas operativos como Unix o derivados (BSD, Linux, Solaris, ...) y otras se pueden desarrollar sin estar a priori integradas dentro del proceso. Repasaremos tambin las ventajas e inconvenientes que tienen los procesos de Erlang. Su estructura aporta ventajas como la posibilidad de lanzar millones de procesos por nodo, teniendo en cuenta que cada mquina puede ejecutar ms de un nodo. Tambin presenta inconvenientes como la velocidad de procesamiento frente a los procesos nativos del sistema operativo. Por ltimo, el sistema de comparticin de informacin entre procesos programados para la concurrencia emplea el paso de mensajes en lugar de emplear mecanismos como la memoria compartida y semforos, o monitores. Para ello proporciona a cada proceso un buzn y la capacidad de enviar mensajes a otros procesos a travs de la sintaxis del propio lenguaje, de una forma simple.
1. Anatoma de un Proceso
Un proceso cualquiera, no slo los que son propios de Erlang, tiene unas caractersticas especficas que lo distingue, por ejemplo, de un hilo. Los procesos son unidades de un programa en ejecucin que tienen un cdigo propio y un espacio de datos propio (normalmente llamado heap). Se podra decir que un proceso cumple los principios del ser vivo, ya que puede nacer (crearse), crecer (ampliando sus recursos asignados),
64
Procesos
reproducirse (generar otros procesos) y morir (terminar su ejecucin). El planificador de procesos de la mquina virtual de Erlang se encarga de dar paso a cada proceso a su debido tiempo y de aprovechar los recursos propios de la mquina, como son los procesadores disponibles, para intentar paralelizar y optimizar al mximo posible la ejecucin de los procesos. Esta sera la vida til de un proceso. En Erlang el proceso es adems un animal social. Tiene mecanismos que le permiten comunicarse con el resto de procesos y enlazarse a otros procesos de forma vital o informativa. En caso de que un proceso muera (ya sea debido a un fallo o porque ya no haya ms cdigo que ejecutar), el proceso que est enlazado con l de forma vital muere tambin, mientras que el que est enlazado de forma informativa es notificado de su muerte. Para esta comunicacin, el proceso dispone de un buzn. En este buzn otros procesos pueden dejar mensajes encolados, de modo que el proceso puede procesar estos mensajes en cualquier momento. El envo de estos mensajes no slo se puede realizar de forma local, dentro del mismo nodo, sino que tambin es posible entre distintos nodos que estn interconectados entre s, ya sea dentro de la misma mquina o en la misma red.
Nota
Cuando se lanza un proceso, en consola podemos ver su representacin, en forma de cadena, como <X.Y.Z>. Los valores que se representan en esta forma equivalen a: X es el nmero del nodo, siendo cero el nodo local. Y son los primeros 15 bits del nmero del proceso, un ndice a la tabla de procesos. Z son los bits 16 a 18 del nmero del proceso. El hecho de que los valores Y y Z estn representados como dos valores aparte, viene de las versiones R9B y anteriores, donde Y era de 15 bits y Z era un contador de reutilizacin. Actualmente Y y Z se siguen representando de forma separada para no romper esa compatibilidad.
2. Ventajas e inconvenientes
Hemos realizado una introduccin rpida y esquemtica de lo que es un proceso en general y un proceso Erlang, para dar una visin a alto nivel del concepto. Como dijimos al principio, los procesos en Erlang no son los del sistema operativo y, por tanto, tienen sus diferencias, sus caractersticas especiales y sus ventajas e inconvenientes. En este
65
Procesos
apartado concretaremos esas ventajas e inconvenientes para saber manejarlos y conocer las limitaciones y las potencias que proporcionan. Desde el principio hemos remarcado siempre que una de las potencias de Erlang son sus procesos, y es porque me atrevera a decir que es el nico lenguaje que dispone de una mquina virtual sobre la que se emplean procesos propios de la mquina virtual y no del sistema operativo. Esto confiere las siguientes ventajas: La limitacin de procesos lanzados se amplia. La mayora de sistemas operativos que se basan en procesos o hilos limitan su lanzamiento a unos 64 mil aproximadamente. La mquina virtual de Erlang gestiona la planificacin de los procesos 1 en ejecucin y eleva ese lmite a 2 millones . La comunicacin entre procesos es ms simple y ms nutrida. La programacin concurrente se basa la comparticin de datos, bien mediante mecanismos como la memoria compartida y el bloqueo de la misma a travs de semforos, o bien mediante la existencia de secciones crticas de cdigo que manipulan los datos compartidos a travs de monitores. Erlang sin embargo emplea el paso de mensajes. Existe un buzn en cada proceso al que se le puede enviar informacin (cualquier dato) y el cdigo del proceso puede trabajar con ese dato de cualquier forma que necesite. Son procesos y no hilos. Cada proceso tiene su propia memoria y por tanto no comparte nada con el resto de procesos. La ventaja principal de tener espacios de memoria exclusiva es que cuando un proceso falla y deja su memoria inconsistente, este hecho no afecta al resto de procesos que pueden seguir trabajando con normalidad. Si el proceso vuelve a levantarse y queda operativo el sistema se autorecupera del error. En el caso de hilos, es posible que un fallo en la memoria (que s es compartida) afecte a ms de un hilo, e incluso al programa entero. No obstante, no todo es perfecto y siempre hay inconvenientes en las ventajas que se pintan. Por un lado, el hecho de que la mquina virtual de Erlang se encargue de los procesos y del planificador de procesos, tiene su coste. Aunque BEAM est bastante optimizada y el rendimiento de la mquina se ha ido incrementando en cada versin liberada de Erlang, cualquier lenguaje que emplee directamente los procesos nativos del sistema operativo ser ms rpido.
1
No obstante, por mquina virtual lanzada el lmite es algo ms bajo por defecto, con el parmetro +P se puede configurar un nmero mayor, siendo el valor de procesos mximo por defecto de 32.768, y pudindose ajustar este valor de 16 a 134.217.727.
66
Procesos
3. Lanzando Procesos
El lanzamiento de los procesos en Erlang se realiza con una construccin del lenguaje, en concreto una funcin para facilitar su compresin y uso (ya que es un BIF o funcin interna) llamado spawn/1. Esta funcin interna se encarga de lanzar un proceso que ejecute el cdigo pasado como parmetro, junto con la configuracin para lanzar el proceso. El retorno a esta llamada es el identificador del proceso lanzado. La identificacin de la funcin, pasada como parmetro a spawn/1 puede realizarse de varias formas distintas. Se puede emplear una clausura o indicar, a travs de una tripleta de datos (mdulo, funcin y argumentos), la funcin que se ejecutar. Las opciones que acepta spawn/1 se refieren sobretodo al nodo Erlang en el que se lanza el proceso y al cdigo para ser ejecutado. La primera parte la veremos un poco ms adelante. Ahora nos centraremos en el lanzamiento del cdigo en el nodo actual. Por ejemplo, si quisiramos ejecutar en un proceso separado la impresin de un dato por pantalla, podramos ejecutar lo siguiente:
> spawn(io, format, ["hola mundo!"]).
Si almacensemos el identificador de proceso llamado comnmente PID en una variable veramos que el proceso ya no est activo mediante la funcin interna is_process_alive/1:
> Pid = spawn(fun() -> io:format("hola mundo!") end). hola mundo!<0.38.0> > is_process_alive(Pid). false
Como dijimos en su definicin un proceso se mantiene vivo mientras tiene cdigo que ejecutar. Obviamente, la llamada a la funcin format/1 termina en el momento en el que imprime por pantalla el texto que se le pasa como parmetro, por lo tanto, el proceso nuevo finaliza en ese momento. Si el cdigo se demorase ms tiempo en ejecutarse, la funcin is_process_alive/1 devolvera un resultado diferente.
2
67
Procesos
4. Bautizando Procesos
Otra de las ventajas disponibles en Erlang sobre los procesos, es poder darles un nombre. Esto facilita mucho la programacin ya que slo necesitamos conocer el nombre de un proceso para poder acceder a l. No es necesario que tengamos el identficador que se ha generado en un momento dado para ese proceso. El registro de los nombres de procesos se realiza a travs de otra funcin interna llamada register/2. Esta funcin se encarga de realizar la asignacin entre el nombre del proceso y el PID para que a partir de ese momento el sistema pueda emplear el nombre como identificador del proceso. El nombre debe de suministrarse como tomo, y cuando se emplee, debe de ser tambin como tomo. Un ejemplo de esto sera el siguiente:
> Pid = spawn(fun() -> timer:sleep(100000) end). <0.53.0> > register(timer, Pid). true
Si ejecutamos esto en la consola, veremos que se queda bloqueada. Esto ocurre porque el proceso est a la espera de recibir un mensaje de otro proceso. La consola de Erlang es tambin un proceso Erlang en s, si escribisemos self/0 obtendramos su PID.
68
Procesos
El envo de un mensaje desde otro proceso se realiza a travs de una construccin simple del lenguaje. Vamos a probar con un el siguiente cdigo:
> Pid = spawn(fun() -> > receive Any -> > io:format("recibido: ~p~n", [Any]) > end > end). <0.49.0> > Pid ! "hola". recibido: "hola" "hola"
El smbolo de exclamacin se emplea para decirle a Erlang que enve al PID que se especifica a la izquierda del signo la informacin de la derecha. La informacin enviada puede ser de cualquier tipo, ya sea un tomo, una lista, un registro o una tupla con la complejidad interna que se desee.
Nota
Cada proceso en Erlang tiene una cola de mensajes que almacena los mensajes recibidos durante la vida del proceso, para que cuando se ejecute receive, el mensaje pueda ser desencolado y procesado.
Para poder realizar una comunicacin bidireccional, el envo debe de agregar el PID de quin enva el mensaje. Si queremos como prueba enviar informacin y recibir una respuesta podemos realizar lo siguiente:
> Pid = spawn(fun() -> > receive > {P,M} -> > io:format("recibido: ~p~n", [M]), > P ! "adios" > end > end). <0.40.0> > Pid ! {self(), "hola"}, > receive > Msg -> > io:format("retorno: ~p~n", [Msg]) > end. recibido: "hola" retorno: "adios"
Con este cdigo, el proceso hijo creado con spawn/1 se mantiene a la escucha desde el momento de su nacimiento. Cuando recibe una tupla con la forma {P,M}, imprime el mensaje M por pantalla y enva el mensaje adios al proceso P.
69
Procesos
El proceso de la consola es quien se encarga de realizar el envo del primer mensaje hacia el proceso con identificador Pid agregando su propio identificador (obtenido mediante la funcin self/0) a la llamada. A continuacin se mantiene a la escucha de la respuesta que le enva el proceso hijo, en este caso adios.
Importante
Las secciones de opcin dentro de receive pueden tener tambin guards. En caso de que el mensaje recibido no concuerde con ninguna de las opciones dadas ser ignorado y se seguir manteniendo el proceso en modo de escucha.
Como opcin de salida para evitar posibles bloqueos en caso de que un evento nunca llegue, o nunca concuerde, o si simplemente se quiere escuchar durante un cierto perodo de tiempo, podemos emplear la seccin especial after. En esta seccin podemos indicarle al sistema un nmero de milisegundos a esperar antes de cesar la escucha, pudiendo indicar un cdigo especfico en este caso. Si por ejemplo, en el cdigo anterior, queremos que el proceso que lanzamos se mantenga slo un segundo en escucha y si no le llega ningn mensaje finalice indicando este hecho, podemos reescribirlo de la siguiente forma:
> Pid = spawn(fun() -> > receive > {P,M} -> > io:format("recibido: ~p~n", [M]), > P ! "adios" > after 1000 -> > io:format("tiempo de espera agotado~n") > end > end). <0.47.0> tiempo de espera agotado
Si ponemos ms segundos y realizamos el envo del mensaje antes de que finalice este perodo, el comportamiento es exactamente igual al anterior. Si dejamos el tiempo pasar, el proceso finalizar su ejecucin informando por pantalla que el tiempo se ha agotado. Desarrollado en forma de mdulo, para aprovechar la recursividad y que el proceso se mantenga siempre activo, podramos hacerlo as:
-module(escucha). -compile([export_all]). escucha() -> receive {Desde, Mensaje} ->
70
Procesos
io:format("recibido: ~p~n", [Mensaje]), Desde ! ok, escucha(); stop -> io:format("proceso terminado~n") after 5000 -> io:format("dime algo!~n"), escucha() end. para(Pid) -> Pid ! stop, ok. dime(Pid, Algo) -> Pid ! {self(), Algo}, ok. init() -> spawn(escucha, escucha, []).
La funcin escucha/0 (del mdulo homnimo) se mantiene a la espera de mensajes. Acepta dos tipos de mensajes. Por un lado el que ya habamos visto antes, una tupla {proceso, mensaje} que recibir desde otro proceso que se comunica con ste (se presentar por pantalla). El otro tipo es un simple mensaje de stop. Cuando se recibe, como ya no volvemos a ejecutar la funcin de escucha/0, el proceso finaliza su ejecucin. Adems, cada 5 segundos desde el ltimo mensaje enviado, o desde el ltimo tiempo agotado, o desde el inicio de la ejecucin, se imprime el mensaje dime algo!, ejecutando recursivamente la funcin escucha/0 para seguir con el proceso activo. El cdigo para utilizar este mdulo podra ser algo como:
> Pid = escucha:init(). <0.34.0> dime algo! > escucha:dime(Pid, "hola"). recibido: "hola" ok dime algo! > escucha:dime(Pid, "hola a todos"). recibido: "hola a todos" ok dime algo! > escucha:para(Pid). proceso terminado
Con este ejemplo queda claro que lanzar un proceso es una actividad trivial, al igual que el intercambio de mensajes entre procesos. Esta es la base sobre la que se fundamenta una de las aplicaciones ms importantes de Erlang, la solucin de problemas en entornos concurrentes. Tambin es la base de la mayora de cdigo que se escribe
71
Procesos
6. Procesos Enlazados
Otra de las funcionalidades que proporciona Erlang respecto a los procesos es la capacidad para enlazarlos funcionalmente. Es posible establecer una vinculacin o enlace vital entre procesos de modo que si a cualquiera de ellos le sucede algo, el otro es inmediatamente finalizado por el sistema. Completando el ejemplo anterior, si el cdigo contuviera un fallo (no de compilacin, sino de ejecucin), el proceso lanzado morira pero al proceso lanzador no le sucedera absolutamente nada. El siguiente fragmento de cdigo contiene un error:
> Pid = spawn(fun() -> A = 5, case A of 6 -> no end end). <0.39.0> =ERROR REPORT==== 27-Apr-2012::19:10:51 === Error in process <0.39.0> with exit value: ...
El error aparece en la consola provocando que el proceso termine inmediatamente. Al proceso principal, el de la consola, no le sucede absolutamente nada. Ni tan siquiera se entera, ya que el proceso fue lanzado sin vinculacin.
Nota
La consola est diseada para procesar las excepciones, por lo que una vinculacin de error con la misma no provoca su cierre por el error recibido, sino que simplemente indica que ha recibido una excepcin de salida.
Cambiando spawn/1 por spawn_link/1 el lanzamiento del proceso se realiza con vinculacin, produciendo:
> Pid = spawn_link(fun() -> A = 5, case A of 6 -> no end end). <0.42.0> =ERROR REPORT==== 27-Apr-2012::19:10:51 === Error in process <0.39.0> with exit value: ... ** exception exit: {case_clause,5}
Vamos a hacer un ejemplo ms completo en un mdulo. Tenemos dos procesos que se mantienen a la escucha por un tiempo limitado y uno de ellos en su cdigo tiene un error. En este caso ambos procesos, aunque independientes, finalizarn, ya que uno depende del otro (as se indica al lanzarlos enlazados).
72
Procesos
Al ejecutar la funcin lanza/0, se genera un nuevo proceso independiente (sin enlazar). Este proceso a su vez genera otro enlazado que ejecuta la funcin zipi/1. Despus se mantiene ejecutando la funcin zape/1. Tendramos pues tres procesos: el de la consola generado por la llamada a lanza/0, el proceso que ejecuta zipi/1 y el proceso que ejecuta zape/1; todos ellos enlazados. Revisando zape/1, podemos ver que cuando el contador llegue a 5, no habr concordancia posible en la sentencia case lo que generar un error que terminar con el proceso. Como est enlazado a zipi/1, este proceso tambin finalizar su ejecucin. Visto desde la consola:
> gemelos:lanza(). zipi - 0 ok zape - 0 zipi - 1 zape - 1 zipi - 2 zape - 2 zipi - 3 zape - 3 zipi - 4 zape - 4 zipi - 5 zape - 5 zipi - 6 >
73
Procesos
=ERROR REPORT==== 30-Oct-2012::22:21:58 === Error in process <0.34.0> with exit value: ...
Analizando la salida, vemos que se imprime zape por pantalla hasta que al evaluar el cdigo se produce un error que termina ese proceso y su enlace, es decir, el proceso zipi. Los enlaces se puede establecer o eliminar a travs de las funciones link/1 y unlink/1. El parmetro que esperan ambas funciones es el PID del proceso a enlazar con el actual en el que se ejecutan. Volviendo sobre nuestro ejemplo anterior, podemos crear un proceso que se encargue de lanzar a los otros manteniendo un enlace con cada uno de ellos. De este modo si uno de ellos finaliza su ejecucin el enlace con el proceso lanzador har que ste finalice por lo que el resto de procesos sern tambin finalizados en cascada. El cdigo del lanzador podra crearse en un mdulo que usara la funcin link/1 de esta forma:
-module(lanzador). -compile([export_all]). init() -> spawn(lanzador, loop, []). loop() -> receive {link, Pid} -> link(Pid); error -> throw(error) end, loop(). agrega(Lanzador, Pid) -> Lanzador ! {link, Pid}, ok.
74
Procesos
En este caso, no hemos introducido un error en el cdigo del mdulo gemelos_lanzador sino que el error se produce durante el procesamiento de uno de los mensajes del lanzador. En concreto, al enviarle el mensaje error al lanzador ste lanza una excepcin produciendo la cada automtica de los tres procesos.
Importante
Para que la finalizacin de un proceso provoque que todos sus enlaces tambin finalicen, debe producirse una finalizacin por error. Si un proceso finaliza su ejecucin de forma normal y satisfactoria, queda finalizado y desenlazado del resto de procesos pero los dems no finalizan. En otras palabras, para que un proceso enlazado sea finalizado por otro, el proceso que provoca la cada de los procesos en cascada debe de haber acabado con un error de ejecucin.
7. Monitorizacin de Procesos
En contraposicin al enlace vital, el enlace informativo o monitorizacin tal y como se conoce en Erlang, permite recibir el estado de cada proceso como mensaje. Este mecanismo permite que podamos conocer si un proceso sigue activo o si ha finalizado su ejecucin, ya sea por un error o de forma normal. Este tipo de enlace es diferente al anterior que simplemente propaga los errores haciendo que se produzcan en todos los procesos enlazados. Un ejemplo simple del paso de mensajes cuando un proceso finaliza se puede ver a travs de este sencillo cdigo:
> {Pid,MonRef} = spawn_monitor(fun() -> receive > Any -> > io:format("recibido: ~p~n", [Any]) > end > end). {<0.58.0>,#Ref<0.0.0.46>} > Pid ! "hola". recibido: "hola" > flush(). Shell got {'DOWN',#Ref<0.0.0.96>,process,<0.58.0>,normal} ok
El primer proceso tiene un receive que lo mantiene en espera hasta que le llegue un mensaje. Al enviarle hola, el proceso finaliza
75
Procesos
satisfactoriamente. La funcin spawn_monitor/1 se encarga de lanzar el nuevo proceso y enlazarle el monitor al proceso de la consola. Cuando ejecutamos la funcin flush/0 podemos ver los mensajes que ha recibido la consola, entre ellos el de finalizacin del proceso lanzado anteriormente. Si queremos lanzar un monitor sobre un proceso ya creado tendramos que recurrir a la funcin monitor/2. El primer parmetro de esta funcin es siempre process y el segundo parmetro ser el PID del proceso a monitorizar. Empleando el ejemplo anterior:
> Pid = spawn(fun() -> receive > Any -> > io:format("recibido: ~p~n", [Any]) > end > end). <0.58.0> > monitor(process, Pid). #Ref<0.0.0.96> > Pid ! "hola". recibido: "hola" > flush(). Shell got {'DOWN',#Ref<0.0.0.96>,process,<0.58.0>,normal} ok
El mensaje de finalizacin enviado por el proceso es una tupla que consta de las siguientes partes:
{'DOWN', MonitorRef, process, Pid, Reason}
La referencia, MonitorRef, es la misma que retorna la funcin monitor/2, el Pid se refiere al identificador del proceso que se est monitorizando y Reason es la razn de terminacin. Si la razn es normal es que el proceso ha finalizado de forma correcta, en caso contrario, ser debido a que encontr algn fallo. El uso de monitores nos puede servir para crear un lanzador como el del apartado anterior pero que, al morir un proceso, sea capaz de relanzarlo cuando se recibe la notificacin de terminacin. Se trata de un monitor que se puede implementar de la siguiente forma:
-module(monitor). -export([init/0, agrega/2]). init() -> Pid = spawn(fun() -> loop([]) end), register(monitor, Pid), ok. loop(State) -> receive {monitor, From, Name, Fun} ->
76
Procesos
Pid = lanza(Name, Fun), From ! {ok, Name}, loop([{Pid,[Name, Fun]}|State]); {'DOWN',_Ref,process,Pid,_Reason} -> [Name, Fun] = proplists:get_value(Pid, State), NewPid = lanza(Name, Fun), io:format("reavivando hijo en ~p~n", [NewPid]), AntiguoHijo = {Pid,[Name,Fun]}, NuevoHijo = {NewPid,[Name,Fun]}, loop([NuevoHijo|State] -- [AntiguoHijo]) end. lanza(Name, Fun) -> Pid = spawn(Fun), register(Name, Pid), monitor(process, Pid), Pid. agrega(Name, Fun) -> monitor ! {monitor, self(), Name, Fun}, receive {ok, Pid} -> Pid end.
El cdigo presente en la clausura no mantiene ningn bucle. Cuando recibe un mensaje se ejecuta presentando por pantalla el texto Hola ...! y finaliza. El proceso monitor recibe la salida del proceso y vuelve a lanzarlo de nuevo, tal y como se observa en los mensajes reavivando hijo en ....
8. Recarga de cdigo
Uno de los requisitos con los que se desarroll la mquina virtual de Erlang fue que el cdigo pudiese cambiar en caliente sin afectar su funcionamiento. El mecanismo para cambiar el cdigo es parecido al que se realiza con los lenguajes de scripting con algunos matices.
77
Procesos
Quizs sea un poco extrao encontrar este tema en un captulo dedicado a procesos, pero nos parece apropiado ya que la recarga de cdigo afecta directamente a los procesos. La recarga de cdigo afecta ms a un proceso que lo emplea de forma continua (como es el cdigo base del proceso), que a otro que lo emplea de forma eventual (funciones aisladas que se emplean en muchos sitios). Pondremos un ejemplo. Teniendo este cdigo:
-module(prueba). -export([code_change/0, init/0]). init() -> loop(). code_change() -> loop(). loop() -> receive Any -> io:format("original: ~p~n", [Any]) end, prueba:code_change().
Se genera un proceso que mantiene el cdigo de loop/0 en ejecucin y atiende a cada peticin que se le enva al proceso. La funcin loop/0 a su vez llama, de forma fully qualified, a la funcin code_change/0. Esta forma de llamar a la funcin le permite a la mquina virtual de Erlang revisar si hay una nueva versin del cdigo en el fichero BEAM y, en caso de ser as, recargarla.
Importante
Erlang puede mantener hasta dos instancias de cdigo en ejecucin. Si tenemos un cdigo ejecutndose que no se llama de forma full qualified, aunque cambiemos el cdigo BEAM no se recargar. Pero si se lanza otro proceso nuevo, se har con la nueva versin del cdigo. En ese momento habr dos instancias diferentes de un mismo cdigo. Si se volviese a modificar el cdigo, el sistema debe de extinguir la versin ms antigua del cdigo para quedarse slo con las dos ltimas, por lo que los procesos antiguos con el cdigo ms antiguo seran eliminados.
78
Procesos
Dado que el proceso est ya en ejecucin, hasta que no provocamos una segunda ejecucin no se ha producido la recarga del cdigo ni comenzado a ejecutar el nuevo cdigo. Es bueno saber que podemos hacer que la recarga de cdigo se haga bajo demanda, utilizando las funciones adecuadas:
-module(prueba). -export([code_change/0]). code_change() -> loop(). loop() -> receive update -> code:purge(?MODULE), code:load_file(?MODULE), ?MODULE:code_change(); Any -> io:format("original: ~p~n", [Any]), loop() end.
Para probar este ejemplo lo lanzamos como la primera vez, haciendo una llamada. Despus cambiamos el cdigo modificando el texto que imprime por pantalla el mensaje y lo compilamos con la orden erlc. Una vez hecho esto podemos provocar la recarga del cdig enviando el mensaje update desde consola fcilmente:
> Pid ! update. update > Pid ! "hola", ok. cambio: "hola" ok
79
Procesos
Esta vez la llamada update nos ahorra el tener que hacer otra llamada adicional para que se ejecute el cdigo nuevo.
9. Gestin de Procesos
Como hemos dicho desde el principio, Erlang ejecuta su cdigo dentro de una mquina virtual, por lo que posee su propia gestin de procesos, de la que ya comentamos sus ventajas e inconvenientes. En este apartado revisaremos las caractersticas de que disponemos para la administracin de procesos dentro de un programa. Aunque ya hemos visto muchas de estas caractersticas como la creacin, vinculacin y monitorizacin, nos quedan otras como el listado, comprobacin y eliminacin. Comenzaremos por lo ms bsico, la eliminacin. Erlang nos provee de una funcin llamada exit/2 que nos permite enviar mensajes de terminacin a los procesos. Los procesos aceptan estas seales y finalizan su ejecucin. El primer parmetro es el PID que es el dato que requiere exit/2 para finalizar el proceso. El segundo parmetro es opcional y representa el motivo de la salida. Por defecto se enva el tomo normal. Su sintaxis por tanto es:
exit(Pid, Reason).
Por otro lado processes/0 nos proporciona una lista de procesos activos. Con process_info/1 obtenemos la informacin sobre un proceso dado el PID e incluso mediante process_info/2 con un parmetro que indica la informacin especfica de la lista de 3 propiedades : enlaces con otros procesos (links), informacin de la memoria usada por el proceso (memory), la cola de mensajes (messages), por quin est siendo monitorizado (monitored_by) o a quin monitoriza (monitors), el nombre del proceso (registered_name), etc.
Toda esta informacin puede ser consultada, con mayor detalle de la siguiente direccin: http:// www.erlang.org/doc/man/erlang.html#process_info-2
80
Procesos
Cada nodo es una instancia en ejecucin de la mquina virtual de Erlang. Esta mquina virtual posee la capacidad de poder comunicarse con otros nodos siempre y cuando se cumplan unas caractersticas concretas: El nodo se debe haber lanzado con un nombre de nodo vlido. La cookie debe de ser la misma en ambos nodos. Deben de poder conectarse, estando en la misma red. Erlang dispone de un mecanismo de seguridad de conexin por clave, a la que se conoce como cookie. La cookie es una palabra de paso que permite a un nodo conectarse con otros nodos siempre que compartan la misma cookie. Un ejemplo de lanzamiento de un nodo Erlang, desde una terminal sera el siguiente:
erl -sname test1 -setcookie mitest
Si lanzamos esta lnea para test1 y test2, veremos que el smobolo de sistema de la consola de Erlang se modifica adoptando el nombre del nodo de cada uno. En caso de que el nombre de la mquina en la que ejecutamos esto fuese por ejemplo bosqueviejo, tendramos dos nodos en estos momentos levantados: test1@bosqueviejo y test2@bosqueviejo. El nombre propio del nodo se obtiene a travs de la funcin interna node/0. Los nodos a los que est conectado ese nodo se obtienen con la funcin interna nodes/0. Los nodos de un cluster se obtienen con la forma:
[node()|nodes()]
Desde la consola podemos usar el siguiente comando para conectar los dos nodos:
(test1@bosqueviejo)> [] (test1@bosqueviejo)> (test1@bosqueviejo)> true (test1@bosqueviejo)> [test2@bosqueviejo] nodes(). Remoto = test2@bosqueviejo, net_kernel:connect_node(Remoto). nodes().
81
Procesos
corre la funcin. Si tenemos otros nodos conectados, podemos realizar programacin paralela o distribuida, lanzando la ejecucin de los procesos en otros nodos. Es lo que se conoce como un proceso remoto. Se puede lanzar un proceso remoto con la misma funcin spawn/1 agregando como primer parmetro el nombre del nodo donde queremos lanzar el proceso. Por ejemplo, si queremos lanzar un proceso que se mantenga a la escucha para dar informacin en el cluster montado por los dos nodos que lanzamos en el apartado anterior, podramos hacerlo con el siguiente cdigo:
-module(hash). -export([init/1, get/2, set/3]). get(Pid, Key) -> Pid ! {get, self(), Key}, receive Any -> Any end. set(Pid, Key, Value) -> Pid ! {set, Key, Value}, ok. init(Node) -> io:format("iniciado~n"), spawn(Node, fun() -> loop([{"hi", "hola"}, {"bye", "adios"}]) end). loop(Data) -> receive {get, From, Key} -> Val = proplists:get_value(Key, Data), From ! Val, loop(Data); {set, Key, Value} -> loop([{Key, Value}|Data]); stop -> ok end.
En la funcin init/1, se agrega el nombre del nodo que se pasa como parmetro a spawn/2. La comunicacin la podemos realizar normalmente como en todos los casos anteriores que hemos visto sin problemas. No obstante, el PID devuelto, a diferencia de los vistos anteriormente, tiene su primera parte distinta de cero lo que indica que est corriendo en otro nodo. Los procesos en nodos remotos no se puede registrar con la funcin interna register/2, es decir, no se les puede asociar un nombre y por ello, son slo accesibles desde el nodo que los lanz.
82
Procesos
Registramos el proceso de la consola con el nombre consola. Desde el otro nodo de Erlang podemos enviar un mensaje de la siguiente forma:
(test2@bosqueviejo)> Remoto = test1@bosqueviejo, (test2@bosqueviejo)> net_kernel:connect_node(Remoto). true (test2@bosqueviejo)> global:whereis_name(consola) ! "hola". "hola"
El envo del mensaje lo podemos realizar a travs del PID o a travs de la funcin send/2 del mdulo global. En todo caso, obtenemos la capacidad de tener accesibilidad a los procesos remotos desde cualquier nodo del cluster.
Nota
Hay muchos casos en los que el mdulo global puede tener un rendimiento bastante bajo, o incluso hasta defectuoso. Por esta 4 razn han aparecido sustitutos como gproc (que requiere del parcheo de parte del cdigo OTP de Erlang), o mdulos que no 5 requieren de ninguna modificacin en la base como nprocreg .
4 5
https://github.com/uwiger/gproc https://github.com/nitrogen/nprocreg
83
Procesos
Aunque el cdigo se ejecuta en el otro nodo, datos como los PID se adaptan a la comunicacin entre nodos, por lo que podramos emplear cualquiera de esos identificadores para obtener informacin del proceso remoto. Cualquier cdigo que se ejecute a travs de este sistema de RPC ser ejecutado en el nodo que se indique como primer parmetro, por lo que el cdigo debe de existir en ese nodo. En caso de que el cdigo resida nicamente en el nodo que solicita la ejecucin remota, existe la posibilidad de exportar el cdigo al nodo donde queremos que se ejecute. Esto puede conseguirse con el siguiente cdigo:
(test1@bosqueviejo)> {hash,B,F} = code:get_object_code(hash). {hash,<<70,79,82,49,0,0,4,0,66,69,65,77,65,116,111,109,0, 0,0,126,0,0,0,17,4,104,97,...>>, "/home/bombadil/hash.beam"} (test1@bosqueviejo)> A = [hash, F, B]. (test1@bosqueviejo)> Host = test2@bosqueviejo, (test1@bosqueviejo)> rpc:call(Host, code, load_binary, A). {module,hash} 6
De esta forma, podramos levantar cada nuevo nodo en cualquier mquina sin tener el cdigo. Todo quedara en llamadas RPC desde el nodo maestro hacia los dems nodos para ir levantando instancias del cdigo y lanzar los procesos que se requieran.
6
84
Procesos
Nota
A travs de multicall/3 en lugar de call/4, del mdulo rpc podemos envar el cdigo a cada uno de los nodos conectados en el cluster.
85
1. ETS
Las siglas ETS se refieren a Erlang Term Storage, o almacenaje de trminos de Erlang. Los trminos ya los habamos revisado anteriormente, por lo que sabemos que se trata de tuplas, en las que el primer elemento de la tupla acta como clave. La razn para crear las tablas ETS fue la de poder almacenar gran cantidad de datos con un tiempo de acceso siempre constante, ya que en los lenguajes funcionales el tiempo de acceso a la informacin suele ser funcin logartmica. Otro motivo fue el proveer al desarrollador de un modo de extraer, almacenar y tratar la informacin con los mecanismos 1 propios del lenguaje . Adems, para que el uso de este sistema fuese ms rpido, las funciones para manejar las funcionalidades de ets se encuentran en formato de BIF. Por todo ello, el almacenaje de trminos Erlang constituye una herramienta fundamental de gestin de la informacin con Erlang, especialmente cuando el tamao de la informacin es elevado y se necesita optimizar los tiempos de acceso.
1
A diferencia de llamadas a sistemas, como el SQL, las tablas ETS se quera que fuesen tratadas con directivas, sentencias y funciones del lenguaje y no enviadas a un subsistema.
86
87
protected El mbito protegido para la ETS garantiza el acceso de lectura a todos los procesos que conozcan el identificador de la ETS, pero slo permite la escritura para el proceso que la cre. public Garantiza el acceso a todos los procesos, tanto para lectura como escritura, a la ETS a travs del identificador de la misma.
Nota
Una ETS mantiene, a nivel de concurrencia, siempre los parmetros de aislamiento y atomicidad ntegros, por lo que asegura que una operacin de escritura sobre un objeto de una ETS, en caso de que sea correcta o falle lo har de forma completa (atomicidad). Cualquier operacin de lectura slo podr ver el conjunto final de las modificaciones en caso de xito (aislamiento).
88
Siendo Pos un nmero entero dentro del rango de elementos de la tupla. heir El sistema puede establecer un proceso hijo al que pasarle el control de la ETS, de modo que si algo le sucediese al proceso que cre la ETS, el proceso hijo recibira el mensaje:
{'ETS-TRANSFER', id, FromPid, HeirData}
En caso de no especificar un heredero, si el proceso propietario de la tabla termina su ejecucin la tabla desaparece con l. Concurrencia Por defecto, las ETS mantienen un nivel de concurrencia por bloqueo completo, es decir, mientras se est trabajando con la tabla ningn otro proceso puede acceder a ella, ya sea para leer o escribir. No obstante, a travs de la opcin:
{read_concurrency, true}
Activamos la concurrencia de lectura. Esta opcin es buena si el nmero de lecturas es mayor que el de escrituras, ya que el sistema adapta internamente los datos para que las lecturas puedan emplear incluso los diferentes procesadores que pueda tener la mquina. Para activar la concurrencia en la escritura, se debe emplear la opcin siguiente:
{write_concurrency, true}
La activacin de la escritura sigue garantizando tanto la atomicidad como el aislamiento. Esta opcin est recomendada si el nivel de concurrencia de lectura/escritura de los datos almacenados en la ETS provocan excesivo tiempo de espera y fallos a consecuencia de este cuello de botella. La nota negativa, vuelve a ser la penalizacin existente al realizar las escrituras concurrentes. compressed Los datos de la ETS se comprimen para almacenarse en memoria. Al trabajar sobre datos comprimidos los procesos de bsqueda y toma
89
de datos son ms costosos. Esta opcin es aconsejable cuando sea ms importante el consumo de memoria que la velocidad de acceso. Como ejemplos de creacin de ETS:
> T = ets:new(prueba, []). 16400 > ets:new(tabla, [named_table]). tabla > ets:new(conjunto, [set, named_table]). conjunto > ets:new(bolsa, [bag, named_table]). bolsa
Todas las opciones vistas anteriormente se pueden emplear en cualquier orden dentro de la lista de opciones, y se pueden poner tantas como se necesite.
Importante
Ante el uso de dos opciones que colisionen, como el hecho de emplear conjuntamente la opcin bag y la opcin set, el sistema emplear la ltima leda de la lista de opciones. Por ejemplo, en este caso:
ets:new(tabla, [set, bag, private, public])
Las opciones que predominan finalmente y las que se quedarn configuradas son bag y public.
Con esta llamada hemos introducido un trmino en el que la clave es rojo dentro de la bolsa. Si queremos ver el contenido de la tabla, podemos emplear la funcin match. Esta funcin emplea dos parmetros: el primero es el nombre de la ETS, y el segundo es el patrn que debe de cumplir el dato para ser mostrado. De momento, daremos '$1' es el comodn que nos permite sacar todos los datos:
90
Si insertamos algunos elementos ms podemos ver cmo se van almacenando del mismo modo:
> ets:insert(bolsa, [{verde,0,255,0},{azul,0,0,255}]). true > ets:match(bolsa, '$1'). [[{rojo,255,0,0}],[{azul,0,0,255}],[{verde,0,255,0}]]
Los elementos se insertan donde mejor conviene al sistema interno, tal y como se puede ver en el listado. Para extraer un elemento concreto dado el identificador de la tupla podemos emplear la funcin lookup/2:
> ets:lookup(bolsa, azul). [{azul,0,0,255}]
Con el uso de las funciones first/1 y next/2, o last/1 y prev/2, podemos recorrer la lista utilizando la recursin. Si llegamos al final nos devolver el tomo '$end_of_table'. Un ejemplo de esto se puede ver en el siguiente mdulo:
-module(ets_show). -compile([export_all]). show_all(Ets) -> show_all(Ets, ets:first(Ets), []). show_all(_Ets, '$end_of_table', List) -> List; show_all(Ets, Id, List) -> show_all(Ets,ets:next(Ets,Id),ets:lookup(Ets,Id) ++ List). main() -> ets:new(bolsa, [named_table, bag]), Colores = [{rojo,255,0,0},{verde,0,255,0},{azul,0,0,255}], ets:insert(bolsa, Colores), show_all(bolsa).
Si ejecutamos la funcin main/0, veremos como nos retorna todo lo insertado dentro de bolsa. Igualmente, si creamos una nueva ETS, podemos emplear la funcin show_all/1 para listar todo su contenido.
91
La teora de la concordancia para las ETS se puede emplear tanto para funciones select/2, como para las funciones match/2. Esta concordancia se basa en pasarle la informacin al ncleo de ETS para realizar la extraccin. Requiere que se puedan identificar variables como tal o bien con el uso de comodines. Para esto se definen dos tomos que tienen una semntica especial para 2 el gestor de las ETS. Son las llamadas variables sin importancia y el comodn. Las variables sin importancia se pueden especificar como '$0', '$1', '$2', ...; La numeracin slo es relevante en caso de especificar la forma en la que se obtendrn los resultados (para las funciones como select/2). El otro tipo de tomo con significado especfico para las ETS es el comodn '_'. Ya vimos en su momento que el signo de subrayado se utiliza para indicar que el dato en esa posicin no interesa. Rescatando el ejemplo anterior, vemos que habamos escrito como parmetro de la funcin match/2 la siguiente expresin:
'$1'
Al no tener $1 forma de tupla, esta expresin de la variable sin importancia concuerda con toda la tupla al completo. Si pusiramos en el match lo siguiente:
{'$1',255,'_','_'}
Veremos que extraemos el valor rojo, ya que es el nico que cumple la condicin de concordancia de tener en su segunda posicin el valor 255. Como la variable sin importancia slo la hemos situado en la primera posicin, slo recibiremos esta.
Nota
Si empleamos la funcin match_object/2 en lugar de match/2 se retornar siempre el objeto completo. La concordancia se tendr en cuenta slo a nivel de eleccin y no a la hora de organizar los datos para su devolucin.
Por ltimo, vamos a ver el uso de la funcin select/2, como una funcin ms avanzada que nos da la posibilidad, no slo de enviar una tupla de concordancia, sino tambin una parte de guardas y la proyeccin (el cmo se visualizarn en el resultado). Esta funcin nos da para las ETS la
2
El nombre de variable sin importancia es una traduccin prestada del ingls don't care al que hace referencia el sitio Learn You Some Erlang [http://learnyousomeerlang.com/].
92
misma potencia que nos brindan las listas de compresin sobre las listas que ya vimos en Seccin1.4, Listas del Captulo2, El lenguaje. El formato que se emplea se denomina especificaciones de concordancia y consta de una tupla de tres elementos: el primero el de la concordancia, ya visto anteriormente, el segundo es el que almacena las guardas y el tercero el que se encarga de especificar la proyeccin de elementos. Comenzaremos con este ejemplo:
{ {'$0','$1','$2','$3'}, [{'<','$1',0}], ['$0'] }
He separado en cada lnea cada uno de los tres parmetros que se deben enviar para cumplir con la especificacin de concordancia. En la primera lnea se puede ver que no se ha realizado ninguna primera criba, sino que se aceptan todas las ETS que tengan ese nmero de tuplas. El segundo parmetro contiene una operacin. Como se puede observar el formato es igual al conocido como calculadora polaca, la operacin es el primer elemento que se encuentra y los operandos los que vienen a continuacin. Volviendo a nuestro ejemplo concreto la condicin que debe de cumplir la tupla es que su elemento '$1' sea mayor que cero. Por ltimo, en el tercer elemento, realizamos una proyeccin para devolver como resultado nico el primer elemento de la tupla (precisamente '$0' el identificador o clave). Otro ejemplo ms complejo o completo se deja a la investigacin del lector:
{ {'$1','$2','$4','$8'}, [{'andalso', {'==','$2',0}, {'==','$8',0} }], [ '$$' ] }
93
Tambin cabe la posibilidad de realizar una eliminacin completa de la tabla con la funcin delete/1, o bien el vaciado de sus elementos con la funcin delete_all_objects/1. Si lo que queremos es eliminar una serie de objetos especficos lo podemos realizar a travs de la funcin match_delete/2. Esta funcin acepta como parmetros la ETS, y el patrn de elementos a eliminar, a igual que en la funcin match/2 ya vista.
En el fichero que se especifica se almacena toda la ETS, con una cabecera en la que se almacenan las opciones con las que fue creada, para que cuando se vuelva a leer el fichero, la ETS se instancie de la misma forma en la que se guard. La funcin file2tab/1 tiene la siguiente forma:
file2tab(Filename) -> {ok, Tab} | {error, Reason}
Se encarga de leer el fichero, tomar la cabecera y crear la ETS con su contenido, tal y como se guard. El almacenamiento de archivos es un buen sistema para poder gestionar los datos de una ETS de manera persistente. No obstante hay que tener en mente que siempre pueden surgir problemas. Por ejemplo el tamao de la ETS puede superar el de la memoria que se puede emplear, o bien el sistema puede fallar antes de que se haya podido guardar la informacin en el fichero. Incluso podra ser que el fichero se corrompiera durante su escritura. Para evitar estos problemas necesitamos un sistema que trabaje directamente con el fichero y que la robustez para haber previsto este tipo de problemas. En Erlang este sistema son las DETS.
2. DETS
Las DETS son ETS que se almacenan en disco (Disk Erlang Term Storage). Al tratarse tambin de almacenaje de trminos, poseen un interfaz al programador muy parecido al de las ETS. El medio de tratamiento y almacenamiento de la informacin es distinto. Las DETS pueden mantener persistencia de informacin mientras que las ETS no la tienen.
94
El sistema DETS se suele emplear cuando se requieren almacenar con cierta persistencia informacin en forma de trminos Erlang y cuyo fichero de almacenaje no exceda los 2 GiB de espacio. Este sistema de almacenaje es el mismo que emplea Mnesia , el motor de base de datos que integra OTP y que viene por defecto con Erlang. Mnesia, como motor de base de datos, no slo posee la capacidad de trabajar con trminos para su almacenaje, sino que proporciona otros elementos como transacciones, consultas, distribucin y fragmentacin de tablas, por lo que, puede ser un entorno ms complejo y potente que si slo se requieren almacenar trminos.
3
Nota
A da de hoy (en la revisin R15 de Erlang/OTP), no existe librera que permita escribir de forma ordenada los trminos hacia disco. Est pendiente y posiblemente en futuras liberaciones veamos que finalmente se agrega a esta lista el conjunto ordenado.
3
En este libro no se tratar Mnesia, porque sino el texto se nos extendera unas decenas de pginas ms y no conseguiramos abarcarlo como se merece.
95
El parmetro Name ser el que le d nombre a la DETS. El nombre debe de ser un tomo como ocurre con las ETS. Este dato ser el que se solicite en el resto de funciones para poder acceder a la DETS. Las opciones que se pueden agregar como segundo parmetro son las siguientes: {access, Access} Como acceso son vlidos los valores read o read_write, siendo este ltimo el que se toma por defecto. {auto_save, AutoSave} Se indica un valor entero que indica el intervalo de autoguardado de la DETS. Es decir, el tiempo en el que se realiza una sincronizacin entre lo que se mantiene en memoria y el disco. Si se especifica infinity, el autoguardado es deshabilitado. La opcin por defecto es 180000 (3 minutos). {min_no_slots, Slots} Es un ajuste de rendimiento que permite especificar en la creacin de la tabla el nmero de claves estimado que sern almacenadas. El valor por defecto es 256. {max_no_slots, Slots} El nmero mximo de slots que ser usado. El valor por defecto y mximo permitido es de 32000000. {keypos, Pos} La posicin dentro del trmino en el que se encontrar la clave de la tupla. {file, File} El nombre del fichero que se usar. Por defecto se toma el nombre de la DETS para la escritura del fichero.
96
{ram_file, boolean()} Si la DETS se mantendr en memoria. Esto quiere decir que la DETS se copia ntegramente a la memoria al momento de abrirla realizando el volcado a disco cuando se cierra. Por defecto esta caracterstica no est activa (false). {repair, true | false | force} Le dice al sistema que debe ejecutar la reparacin de la DETS al abrirla, en caso de que no se hubiese cerrado correctamente. Por defecto est activa (true). En caso de que se indique false y se requiera reparacin, se retornar el error:
{error, {needs_repair, File}}
El valor force quiere decir que la reparacin se llevar a cabo aunque la tabla haya sido cerrada correctamente. {type, Tipo} El tipo de la tabla, tal y como vimos en la seccin anterior.
Importante
Para no perder informacin, es importante que siempre cerremos la DETS de forma apropiada, a travs de la funcin close/1. Si no lo hacemos, al volver a ejecutar el programa, es seguro que se requerir una reparacin del fichero e incluso podran llegar a perderse datos.
Al igual que con las ETS, las funciones info/1 e info/2, nos proporcionan informacin sobre la DETS abierta dado su nombre. La ltima apertura, como se puede ver, nos origina un error. Esto es debido a que se ha intentado abrir un fichero llamado conjunto que no
97
existe. Como el acceso que se da es de slo lectura (read), no se le est capacitando para crear el fichero y por tanto se origina el error.
Importante
Si se intenta abrir el mismo fichero desde dos partes diferentes del cdigo con los mismos parmetros, el fichero es abierto sin problemas pero slo la primera vez, el segundo usa esta primera instancia. Si alguna de las partes cerrase el fichero, como la instancia tiene reflejado dos usos, se mantiene abierta hasta que la otra parte tambin cierre el fichero.
Con el uso de esta funcin los datos de la DETS son volcados en la ETS. Cabe destacar que es necesario haber abierto la DETS con anterioridad. Los datos que contenga la ETS previamente no se eliminan, a menos que colisionen con los que vienen de la DETS en cuyo caso se sobreescribirn. La especificacin de la funcin from_ets/2 es:
from_ets(Name, EtsTab) -> ok | {error, Reason}
98
En este caso, la DETS s es vaciada (se eliminan todos sus elementos) y a continuacin se inserta la ETS tal cual dentro de la DETS.
Nota
En ambos casos, el orden en el que se guardan los elementos es indeterminado, tanto de DETS a ETS, como en el caso opuesto.
3. Ficheros
En este apartado revisaremos lo que se puede hacer desde Erlang con los ficheros. Para ello, vamos a diferenciar el tratamiento de los ficheros entre los dos tipos existentes: binarios y de texto. Los ficheros de texto tienen un tratamiento especial, ya que se interpretan algunos caracteres especiales como fin de fichero, salto de lnea, e incluso se pueden tomar ficheros formateados de cierta forma para que se carguen como formas de datos de Erlang. En cambio, los ficheros binarios, tienen un tamao definido y todos sus bytes son iguales, es decir, no tienen ningn significado especial, aunque se pueden agrupar para definir formas de datos estructuradas.
Como primer parmetro tenemos el nombre del fichero (Filename), y el segundo parmtro corresponde a una lista en la que se pueden agregar tantas opciones de las siguientes como se necesite: read | write | append | exclusive El fichero se puede abrir en modo de lectura (read), escritura (write) o para agregacin al final (append). El caso de exclusive, es usado para crear el fichero, devolviendo un error en caso de que ya exista.
99
raw Abre el fichero mucho ms rpido, ya que ningn proceso de Erlang se encarga de manejar el fichero. Sin embargo, este modo de trabajo tiene limitaciones, como que las funciones del mdulo io no pueden ser empleadas o que slo el proceso que haya abierto el fichero puede utilizarlo. binary Las operaciones de lectura retornarn listas binarias en lugar de listas. {delayed_write, Size, Delay} Los datos se mantienen en un buffer hasta que se alcanza el tamao indicado por Size o hasta que el dato ms antiguo en el buffer es de ms all del tiempo especificado en Delay, entonces se escriben a disco. Esta opcin se emplea para decrementar los accesos a disco y, por lo tanto, intentar incrementar el rendimiento del sistema. {read_ahead, Size} Activa el buffer de lectura para las operaciones de lectura que son inferiores al tamao definido en Size. Igual que en el caso anterior, decrementa el nmero de llamadas al sistema para acceso a disco, por lo que aumenta el rendimiento. compressed Crea o abre ficheros comprimidos con gzip. Esta opcin puede combinarse con read o write, pero no ambas. {encoding, Encoding} Realiza la conversin automtica de caracteres para y desde un tipo especfico. La codificacin por defecto es latin1. Las tablas de codificacin permitidas se pueden revisar en la documentacin 4 oficial de la funcin open/2 . Un ejemplo de apertura de un fichero tan famoso como /etc/ debian_version, para lectura o escritura y el resultado obtenido:
> file:open("/etc/debian_version", [read]). {ok,<0.52.0>} > file:open("/etc/debian_version", [write]). {error,eacces}
4 5
http://www.erlang.org/doc/man/file.html#open-2 Para los que usan Debian o Ubuntu, o alguna distribucin derivada de estas, es frecuente encontrar el fichero /etc/debian_version en el sistema de ficheros.
100
Nota
Vemos que el retorno de la primera operacin que se realiza correctamente, nos devuelve un PID. Al no haber empleado la opcin raw se crea un proceso Erlang intermedio que se encarga de la informacin del fichero y de realizar los accesos de lectura y escritura.
Al intentar abrir un fichero para escritura hemos obtenido un error de acceso (eaccess) debido a la falta de permisos, ya que un usuario normal no tiene permisos para escribir en ese fichero. Para cerrar el fichero, y con ello liberar el proceso que se mantiene a la espera de indicaciones para tratar dicho fichero, debemos de emplear la funcin close/1. En los ejemplos anteriores, sera hacer lo siguiente:
> {ok, Pid} = file:open("/etc/debian_version", [read]). {ok,<0.34.0>} > file:close(Pid). ok
Importante
Es importante que cerremos todos los ficheros que abramos ya que esto repercute, no slo en un uso innecesario de los recursos 6 de los descriptores de ficheros , sino tambin de procesos, ya que cada fichero abierto de un modo no raw lleva asociado un proceso Erlang.
Son las estructuras del sistema operativo que se emplean para designar que un programa tiene un fichero abierto.
101
> {ok, Pid} = file:open("/etc/motd", [read]). {ok,<0.34.0>} > file:read_line(Pid). {ok,"Linux barbol 3.1.0-1-amd64 Tue Jan 10 05:01:58 UTC..."} > file:read_line(Pid). {ok,"\n"} > file:read_line(Pid). {ok,"The programs included with the Debian GNU/Linux..."} > file:read_line(Pid). {ok,"the exact distribution terms for each program are..."} > file:read_line(Pid). {ok,"individual files in /usr/share/doc/*/copyright.\n"} > file:read_line(Pid). {ok,"\n"} > file:read_line(Pid). {ok,"Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY..."} > file:read_line(Pid). {ok,"permitted by applicable law.\n"} > file:read_line(Pid). eof > file:close(Pid). ok
Si lo que queremos es leer todo el contenido del fichero para almacenarlo en una variable de texto, podemos emplear la funcin read_file/1:
> file:read_file("/etc/debian_version"). {ok,<<"wheezy/sid\n">>}
Nota
Al igual que hemos empleado read_line/1, podemos emplear io:get_line/2 para realizar la lectura, pasando como primer parmetro el identificador del fichero abierto.
Por ltimo, si el contenido del fichero que queremos leer contiene elementos de Erlang, como trminos o listas, separados por un punto cada uno de los elementos base que conforman el documento, este puede ser ledo y evaluado como datos Erlang directamente. Esto se realiza con al funcin consult/1. Esta funcin puede leer un fichero como el que se muestra a continuacin:
{nombre, "Manuel"}. {apellido1, "Rubio"}. {apellido2, "Jimenez"}.
102
Con esta sintaxis crearemos archivos que podamos emplear como configuracin, parametrizacin o salvaguarda de informacin, cuyos datos podemos rescatar en cualquier momento.
Si por contra lo que queremos es simplemente escribir un texto ya almacenado dentro de una cadena, podemos simplificar empleando la funcin write/2 tal y como se ve en este ejemplo:
> {ok, Pid} = file:open("mifile.txt", [write]). {ok,<0.42.0>} > file:write(Pid, "fichero de texto"). ok > file:close(Pid). ok
Tambin es posible utilizar ambos mtodos realizando escrituras combinadas, es decir, primero una y despus la otra, tantas veces como queramos o necesitemos.
103
fichero abierto y, como segundo parmetro, el tamao del fichero que ser ledo. Aunque podemos emplear otras funciones, esta es la ms genrica y nos permitir realizar las lecturas de los ficheros sin problemas. Si queremos leer la totalidad del fichero es ms aconsejable emplear la funcin read_file/1, ya que nos auna la apertura, lectura y cierre del fichero en una sola funcin. Un ejemplo de lectura de una imagen sera la siguiente:
> Filename = "/usr/share/pixmaps/debian-logo.png". "/usr/share/pixmaps/debian-logo.png" > {ok, Pid} = file:open(Filename, [read,binary]). {ok,<0.34.0>} > file:read(Pid, 16). {ok,<<137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82>>} > file:close(Pid). ok
Los bytes ledos de nuestro fichero binario imagen se muestran como una lista binaria de enteros. En concreto hemos ledo 16 bytes del principio del archivo PNG. Podemos leer 8 bytes que conforman la firma del PNG y los 8 que contienen la cabecera del PNG en la siguiente forma (como 7 podemos ver en la wikipedia ):
> Filename = "/usr/share/pixmaps/debian-logo.png". "/usr/share/pixmaps/debian-logo.png" > {ok, Pid} = file:open(Filename, [read,binary]). {ok,<0.34.0>} > {ok, <<137,"PNG",13,10,26,10>>} = file:read(Pid, 8). {ok,<<137,80,78,71,13,10,26,10>>} > {ok, <<Length:32, "IHDR">>} = file:read(Pid, 8). {ok,<<0,0,0,13,73,72,68,82>>} > {ok, <<Width:32, Height:32, Depth:8, Color:8, > Compression:8, Filter:8, Interlace:8>>} > = file:read(Pid, Length). {ok,<<0,0,0,48,0,0,0,48,8,6,0,0,0>>} > io:format("Image ~bx~b pixels~n", [Width,Height]). Image 48x48 pixels ok > file:close(Pid). ok
Siguiendo las directrices de la especificacin de los ficheros PNG, hemos podido extraer, gracias a las listas binarias y al tratamiento que se puede realizar con los bits, el tamao de la imagen y muchos otros datos que podramos tambin pasar por pantalla. Es aconsejable realizar el tratamiento de ficheros binarios siempre a travs de listas binarias. En el ejemplo de la lectura del PNG hemos
7
http://en.wikipedia.org/wiki/Portable_Network_Graphics
104
visto cmo se desempaqueta el entramado de bytes para obtener la informacin codificada en el fichero. Teniendo la definicin de otros tipos de documentos binarios se podra hacer lo mismo para ficheros de audio como los WAV o MP3, o para ficheros de vdeo como los AVI o MOV.
Para modificar una parte especfica del fichero, habr que desplazar el puntero al punto exacto donde queremos escribir. Esto es lo que se conoce como escrituras y/o lecturas aleatorias (o no secuenciales). La funcin que permite realizar este tipo de movimientos por el fichero es position/2 (del mdulo file). Esta funcin nos permite desplazarnos por el fichero a posiciones absolutas (o relativas al principio del fichero, tambin llamado bof, o begin of file), relativas a la posicin actual: cur; o relativas al final del fichero: eof. Los parmetros que podemos emplear son:
105
> {ok, Pid} = file:open("logo.png", [binary,write,read]). {ok,<0.99.0>} > file:position(Pid, 1024). {ok,1024} > file:position(Pid, {cur, -24}). {ok,1000} > file:position(Pid, eof). {ok,1718} > file:position(Pid, {eof, 24}). {ok,1742} > file:position(Pid, {eof, -24}). {ok,1694} > file:position(Pid, bof). {ok,0} > file:close(Pid). ok
Vemos que incluso podemos desplazarnos ms all del tamao del fichero (que en ese caso es de 1718 bytes), debido a que hemos abierto el fichero para escritura y se nos permite agregar ms informacin para ampliar el tamao del fichero.
Con esta secuencia indicamos la posicin y lo que deseamos escribir en cada punto. Esto se pasara como segundo parmetro en la funcin pwrite/2. De forma anloga, tambin se puede dar la lista de direcciones para realizar las lecturas oportunas, es decir, para pread/2, de la siguiente forma:
106
En este caso, en lugar de especificar contenido para escribir, especificamos el tamao para leer a partir de la posicin dada.
Importante
En caso de emplear listas de caracteres, hay que tener especial cuidado con los caracteres de UTF-8, ya que algunos emplean dos bytes para su almacenaje en lugar de slo uno y esto puede provocar que el cmputo de la posicin sea errneo (o susceptible de errores). Para evitar estos errores, aconsejo emplear el acceso aleatorio tan slo en ficheros binarios, ya que las unidades estn mejor definidas en este caso.
4. Gestin de Ficheros
Adems de todo lo visto anteriormente para la creacin, modificacin y lectura de un fichero, podemos realizar ms acciones an con estos ficheros, como puede ser: renombrarlos, cambiar sus permisos, 8 propietario , copiar el fichero, truncarlo o eliminarlo.
El cambio de permisos y propietario depende de cada sistema operativo y los permisos en s que tenga el usuario que lanz la ejecucin del programa.
107
".png"
Como en otros lenguajes basename/1 nos retorna el nombre del fichero sin ruta. dirname/1 nos devuelve la ruta sin el nombre del fichero. Tambin tenemos rootname/1 que nos retorna el nombre del fichero (con ruta si dispone de ella) y extension/1 que nos da nicamente la extensin (con el punto includo). Tambin disponemos de la funcin absname/1 que retorna siempre el nombre del fichero de forma absoluta. Si le pasamos una ruta relativa o un fichero sin ruta, obtenemos un nombre de fichero absoluto, completado con la ruta de trabajo actual:
> filename:absname("logo.png"). "/home/bombadil/logo.png"
La funcin de rename/2, adems de para cambiar el nombre del fichero, nos puede servir para cambiar la ubicacin del fichero si indicamos una ruta distinta, por ejemplo:
file:rename("logo.png", "/tmp/logo.png")
Nota
Las operaciones se realizan sobre ficheros especficos, no sobre grupos de ficheros como los comandos de consola de los sistemas operativos, por lo que el uso de comodines como asterisco (*) o interrogante (?) no se tienen en cuenta como tal, sino que son interpretados como parte del nombre del fichero.
En caso de que no queramos eliminar un fichero (porque un programa lo tenga abierto o por otro motivo) y slo queramos eliminar su contenido
108
y reiniciar sus punteros a la posicin cero, esto lo podemos realizar mediante el uso de la funcin truncate/1.
Por lo que si queremos que el fichero logo.png tenga permisos de lectura y escritura para su propietario y lectura para el grupo y otros, tendremos que ejecutar:
file:change_mode("logo.png", 8#00644)
El cambio de propietario se puede realizar a travs de la funcin change_owner/2 o change_owner/3 si adems queremos cambiar el 9 grupo. Los parmetros de UID y GID se dan en formato entero . Hay una funcin que engloba todas las funciones del mdulo file para la gestin de usuarios, grupos y permisos y permite realizar
9
En los sistemas de tipo Unix este dato se puede ver en /etc/passwd donde hay una correspondencia entre el nombre del usuario y su UID.
109
todas las modificaciones en una sola accin, tanto para la lectura: read_file_info/1; como para la escritura: write_file_info/2. Vamos a verlas un poco ms en detalle: read_file_info/1 Permite leer las propiedades de un fichero retornando un registro en el que aparecen datos como la fecha y hora de creacin, fecha y hora de modificacin y fecha y hora del ltimo acceso, adems de los permisos, tipo de fichero y tamao del mismo. Por ejemplo:
> file:read_file_info("logo.png",size). {ok,#file_info{size=1718,type=regular, access=read_write, atime={{2012,7,18},{14,23,1}}, mtime={{2012,7,18},{14,23,1}}, ctime={{2012,7,18},{14,23,1}}, mode=33188,links=1,major_device=2049, minor_device=0, inode=11150880,uid=1000,gid=1000}}
write_file_info/2 Permite modificar cualquiera de los datos del fichero, para ello, se debe de especificar, como segundo parmetro, un registro de tipo file_info y rellenarlo con los datos del fichero que deseemos modificar.
5. Gestin de Directorios
Hasta el momento hemos visto como trabajar con ficheros, su contenido ya sea de tipo texto o de tipo binario, as como la gestin propia de los ficheros (copia, renombrado, eliminacin, ...), ahora vamos a tratar la gestin de los directorios. Los directorios nos permiten organizar nuestros ficheros de una forma ms categorizada. Para sistemas que trabajan con miles de ficheros esto no es una opcin sino una necesidad, ya que, en el terreno informtico no hay recursos infinitos e incluso el nmero de ficheros que pueden 10 albergarse en un directorio est limitado . Como la gestin de los directorios, e incluso la de los ficheros se puede realizar desde un programa, un sistema que cree muchos ficheros puede particionar estos en directorios y subdirectorios, de modo que el acceso a cada directorio sea ms rpido que en el caso de tener un directorio con miles de ficheros.
10
Hay sistemas de ficheros que establecen este lmite a 1024 y otros que permiten miles o millones de ficheros por directorio, pero esto no es nada aconsejable, ya que el tratamiento y gestin del propio directorio o de los propios ficheros puede ser extremadamente lento.
110
Veremos a continuacin las funciones relativas a la gestin de directorios bajo los conceptos en los que se emplean.
La ruta inicial se sita en el directorio en el que nos encontrsemos al ejecutar la consola de Erlang.
En sistemas como la shell de los sistemas tipo Unix, se permite realizar el comando:
mkdir -p /miruta/nuevo1/nuevo2/midir
Con lo que no slo se crea un directorio, sino todo los necesarios hasta llegar al ltimo indicado en la ruta pasada como parmetro. Esto no lo realiza make_dir/1. Esta funcin debe de recibir una ruta existente y
111
crear el ltimo directorio que se indique en la ruta, siempre que no exista ya. En caso de que quisiramos crear un directorio con todos sus directorios padres, en caso de que no existan, podemos emplear la funcin ensure_dir/1 del mdulo filelib. Esta funcin crea todos los directorios necesarios para que la ruta exista. Podemos ver un ejemplo:
> file:make_dir("/tmp/prueba/dir1"). {error,enoent} > filelib:ensure_dir("/tmp/prueba/dir1/"). ok
Partimos de que /tmp/prueba no existe. Por este motivo la funcin make_dir/1 no puede crear el directorio final, dir1. La funcin ensure_dir/1, crea ambos directorios, primero prueba y despus dir1 dentro de prueba.
Importante
El parmetro de ensure_dir/1 debe de terminar en barra para que cree hasta el ltimo directorio, ya que la funcin est creada con la idea de que se pueda pasar como parmetro la ruta de un fichero (con el nombre del fichero includo) y cree el directorio para albergar al fichero:
filelib:ensure_dir("/tmp/midir/logo.png")
5.3. Es un fichero?
Para emplear en las guardas (o guards), al igual que disponemos de las funciones is_list/1, podemos hacer uso de las funciones del mdulo filelib: is_dir/1 o is_file/1. Esto nos permite realizar funciones que nos permitan realizar un proceso previo de validacin, por ejemplo, en caso de las configuraciones en las que se nos proporciona una ruta:
temp_dir_config(Dir) when not is_dir(Dir) -> ok = filelib:ensure_dir(Dir ++ "/"), temp_dir_config(Dir); temp_dir_config(Dir) -> to_do.
Igualmente podemos emplear las funciones file_size/1 (tamao del fichero) o last_modified/1 (la ltima fecha de modificacin) para agregar ms semntica o funcionalidad al cdigo.
112
Otra forma de obtener los ficheros que nos interesan que podemos encontrar dentro del mdulo filelib es a travs de la funcin wildcard/1, la cual nos permite, no slo poner una ruta, sino adems emplear los comodines para obtener los ficheros que concuerden:
> filelib:wildcard("/home/bombadil/*.png"). ["/home/bombadil/logo.png"]
Con estos listados, a travs de funciones sobre listas como map/2, podemos realizar un procesado individualizado de los ficheros que nos retornen las funciones. Por ejemplo, si queremos extraer, adems del nombre del fichero el tamao y mostrarlo:
> lists:map(fun(X) -> > io:format("~-30s ~w~n", [X,filelib:file_size(X)]) > end, filelib:wildcard("/home/bombadil/*.png")). /home/bombadil/logo.png 1718
Esto nos abre una cantidad de posibilidades para obtener informacin de los ficheros albergados en un directorio, o incluso para poder recorrer directorios a travs de funciones recursivas, en las que poder emplear las guardas vistas referentes a los ficheros.
113
Captulo 7. Comunicaciones
No hay lugares remotos. En virtud de los medios de comunicacin actuales, todo es ahora. Herbert Marshall Mcluhan Uno de los principales cometidos de un servidor, es establecer puertos de comunicacin para recibir conexiones entrantes. La comunicacin se establece a varios niveles, empleando en cada uno de los niveles un protocolo especfico para la comunicacin. En este captulo nos centraremos en el protocolo IP, TCP y UDP para la pila de conexiones ms popular: TCP/IP.
114
Comunicaciones
que lleguen a su destino y los acuses de recibo para asegurar de que el paquete es recibido correctamente. En este nivel veremos dos protocolos: TCP y UDP. Nivel de aplicacin Este es el nivel ms alto que podemos encontrar en comunicacin. Aqu se definen y usan protocolos como: HTTP, FTP, SMTP, POP3, IMAP, etc.
1.1. Direcciones IP
Una direccin IP se representa mediante un nmero binario de 32 bits (segn IPv4). Las direcciones IP se pueden expresar como nmeros de notacin decimal: se dividen los 32 bits de la direccin en cuatro octetos. 1 El valor decimal de cada octeto puede estar entre 0 y 255 . En la expresin de direcciones IPv4 en decimal se separa cada octeto por un punto. Cada uno de estos octetos puede estar comprendido entre 0 y 255, salvo algunas excepciones. Los ceros iniciales, si los hubiera, se pueden obviar. Ejemplo de representacin de direccin IP: 164.12.123.65
Importante
Las direcciones IP en Erlang se emplean a travs de un formato de tupla formada por cuatro elementos enteros. Esta forma es en la que generalmente trabaja el mdulo inet que es el que se encarga de las comunicaciones, tanto para conexiones cliente, como para servidor:
{127,0,0,1}
Este sera el formato de IP para 127.0.0.1. La funcin inet:ip/1 nos permite realizar la conversin del formato de texto al formato de tupla.
Hay tres clases de direcciones IP que una organizacin puede recibir de parte de la Internet Corporation for Assigned Names and Numbers (ICANN): clase A, clase B y clase C. En la actualidad, ICANN reserva 2 las direcciones de clase A para los gobiernos de todo el mundo y las direcciones de clase B para las medianas empresas. Se otorgan direcciones de clase C para todos los dems solicitantes. Cada clase de red permite una cantidad fija de equipos (hosts).
1
El nmero binario de 8 bits ms alto es 11111111 y esos bits, de derecha a izquierda, tienen valores decimales de 1, 2, 4, 8, 16, 32, 64 y 128, lo que suma 255 en total. 2 Aunque en el pasado se le hayan otorgado a empresas de gran envergadura como, por ejemplo, Hewlett Packard.
115
Comunicaciones
En una red de clase A, se asigna el primer octeto para identificar la red, reservando los tres ltimos octetos (24 bits) para que sean asignados a los hosts, de modo que la cantidad mxima de hosts es 224 menos dos: las direcciones reservadas de broadcast (tres ltimos octetos a 255) y de red (tres ltimos octetos a 0), es decir, 16.777.214 equipos. En una red de clase B, se asignan los dos primeros octetos para identificar la red, reservando los dos octetos finales (16 bits) para que sean asignados a los equipos, de modo que la cantidad mxima de equipos es 216 (de nuevo menos dos), lo que equivale a 65.534 equipos. En una red de clase C, se asignan los tres primeros octetos para identificar la red, reservando el octeto final (8 bits) para que sea asignado a los equipos, de modo que la cantidad mxima de equipos es 28 (menos dos), o 254 equipos. La direccin 0.0.0.0 es utilizada por las mquinas cuando estn arrancando o no se les ha asignado direccin. La direccin que tiene a cero su parte destinada a equipos sirve para definir la red en la que se ubica. Se denomina direccin de red. La direccin que tiene a uno todos los bits de su parte de equipo sirve para comunicar con todos los equipos de la red en la que se ubica. Se denomina direccin de broadcast. Las direcciones 127.x.x.x se reservan para pruebas de retroalimentacin. Se denomina direccin de bucle local o loopback. Hay ciertas direcciones en cada clase de direccin IP que no estn asignadas y que se denominan direcciones privadas. Las direcciones privadas pueden ser utilizadas por los equipos que usan traduccin de direccin de red (NAT) para conectarse a una red pblica o por los hosts que no se conectan a Internet. En una misma red no pueden existir dos direcciones iguales, pero s se pueden repetir en dos redes privadas que no tengan conexin entre s directamente. Las direcciones privadas son: Clase A: 10.0.0.0 a 10.255.255.255 (8 bits red, 24 bits equipos) Clase B: 172.16.0.0 a 172.31.255.255 (16 bits red, 16 bits equipos) Clase C: 192.168.0.0 a 192.168.255.255 (24 bits red, 8 bits equipos) Muchas aplicaciones requieren conectividad dentro de una sola red, y no necesitan conectividad externa. En las redes de gran tamao a menudo se usa TCP/IP. Por ejemplo, los bancos pueden utilizar TCP/ IP para conectar los cajeros automticos que no se conectan a la red
116
Comunicaciones
pblica, de manera que las direcciones privadas son ideales para ellos. Las direcciones privadas tambin se pueden utilizar en una red en la que no hay suficientes direcciones pblicas disponibles. Las direcciones privadas se pueden utilizar junto con un servidor de traduccin de direcciones de red (NAT) para suministrar conectividad a todos los equipos de una red que tiene relativamente pocas direcciones pblicas disponibles. Segn lo acordado, cualquier trfico que posea una direccin destino dentro de uno de los intervalos de direcciones privadas no se enrutar a travs de Internet.
1.2. Puertos
Los puertos de comunicaciones son la base sobre la que se sustentan los protocolos de transporte TCP y UDP. Estos protocolos establecen conexiones salientes y entrantes en puertos denominados activos o pasivos respectivamente. Los puertos se representan como nmeros en rango de 16 bits, que pueden ir desde el 0 hasta el 65535. Los puertos por debajo del 1024 se denominan puertos privilegiados y en sistemas como los UNIX se requieren permisos de super-usuario (o root) para poder emplear estos 3 puertos .
Nota
En la mayora de sistemas operativos existe un fichero de texto plano denominado services que contiene, formateado en dos columnas: el nombre de servicio y el puerto que emplea dicho servicio. La columna del puerto, adems, viene formateada de forma que se indica el nmero, una barra inclinada y el tipo de transporte que se emplea:
http http ftp-data ftp domain domain 80/tcp 80/udp 20/tcp 21/tcp 53/tcp 53/udp
Cada protocolo de transporte puede hacer uso del rango de numeracin sin colisionar con ningn otro. TCP puede hacer uso del puerto 22 e igualmente UDP podra usar el mismo sin provocar colisin. La asignacin de puertos es realizada por los protocolos de transporte, cada uno mantiene su propia numeracin.
3
Esto es debido tambin a que la mayora de servicios que se prestan estn en este rango, de modo que en un servidor un usuario sin privilegios no pueda establecer un puerto pasivo en el puerto 80 (dedicado a HTTP), 25 (de SMTP), 22 (de SSH) o 21 (de FTP) entre otros.
117
Comunicaciones
El puerto activo toma de la numeracin de puertos un nmero y lo reserva para establecer la comunicacin con el puerto pasivo. El nmero del puerto activo puede ser elegido o no dependiendo del protocolo de transporte. La comunicacin se identifica para el sistema operativo con el par de puertos activo/pasivo adems de la direccin IP tanto de origen como de destino. Esto hace posible que a un puerto pasivo se pueda conectar ms de un puerto activo.
Nota
En la jerga de los sistemas de comunicacin existen algunas palabras clave que se emplean para determinar ciertos aspectos de la comunicacin o los elementos que la componen. La palabra anglosajona socket (traducida como zcalo o conector) es la palabra que se suele emplear para indicar una conexin. Cuando se establece un puerto pasivo mediante TCP se suele decir que el servidor escucha mientras que si es mediante UDP se dice que el servidor est enlazado a ese puerto.
En el esquema cliente-servidor existe un servidor que se mantiene a la espera de una peticin entrante y un cliente de forma activa realizas las peticiones al servidor. El servidor empleara un puerto pasivo para establecer la comunicacin mientras que el cliente usara uno activo. Como hemos ido comentando a lo largo de la seccin hay dos protocolos para transporte que emplean los puertos de comunicacin: TCP Es orientado a conexin. Requiere que el cliente formalice la conexin con el servidor y esta se mantiene hasta que una de las dos partes solicita la desconexin o durante un envo se produzca un tiempo de espera agotado. UDP UDP es un protocolo de datagramas. Un datagrama puede ser enviado hacia un servidor pero no se comprueba el estado de recepcin. No se espera respuesta del mismo. El tratamiento de este tipo de paquetes carga menos la red y los sistemas operativos pero si la red no es fiable puede existir prdida de informacin.
118
Comunicaciones
entrantes. Tambin permite emplear un puerto activo para el envo de un paquete hacia un servidor que se mantenga enlazado. Resumiendo, permite tanto programar servidores como clientes UDP. En UDP tanto cliente como servidor deben enlazar un puerto. El servidor lo har de forma pasiva para recibir mensajes. El cliente lo har de forma activa para enviarlos y tener la posibilidad de recibir respuesta del servidor. El mdulo gen_udp aprovecha las capacidades propias de los procesos enviando cada paquete recibido al proceso que solicit el enlace con el puerto. Hay tres funciones que emplearemos con mucha frecuencia en la construccin de servidores y clientes UDP: open/1 o open/2, send/4 y close/1. La funcin open/2 se presenta:
open(Port, Opts) -> {ok, Socket} | {error, Reason}
Puede recibir como segundo parmetro opciones que permiten variar la forma en la que establecer el enlace con el puerto. Las opciones son: list | binary Indica si se quiere recibir el paquete como una lista o un binario. El valor por defecto es list. {ip | ifaddr, ip_address()} La direccin IP en la que enlazar el puerto. inet | inet6 Emplea IPv4 o IPv6 para las conexiones. El valor por defecto es inet. {active, true | false | once} Indica si todos los mensajes recibidos por red sern pasados al proceso (true) o no (false) o si se har slo la primera vez (once). El valor por defecto es true. {reuseaddr, true | false} Permite reutilizar el puerto. No lo bloquea. Por defecto esta opcin est deshabilitada.
119
Comunicaciones
Nota
Hay disponibles muchas opciones ms a las que no entraremos ya que son conceptos ms avanzados o muy especficos, con lo que salen del mbito de explicacin de este captulo. Si desea ms informacin sobre la funcin gen_udp:open/2 puede revisar la siguiente direccin: http://www.erlang.org/doc/man/gen_udp.html#open-2
Pondremos en prctica lo aprendido. Vamos a escribir un mdulo que enlace el puerto 2020 y cada paquete que reciba lo pase por pantalla:
-module(udpsrv). -export([start/1, init/1, loop/1]). -record(udp, {socket, ip, port, msg}). start(Port) -> spawn(?MODULE, init, [Port]), ok. init(Port) -> {ok, Socket} = gen_udp:open(Port), loop(Socket). loop(Socket) -> receive stop -> gen_udp:close(Socket); Packet when is_record(Packet, udp) -> io:format("recibido(~p): ~p~n", [ Packet#udp.ip, Packet#udp.msg ]), #udp{ip=IP,port=Port} = Packet, gen_udp:send(Socket, IP, Port, "recibido"), loop(Socket) end.
El cdigo se inicia mediante la funcin start/1 indicando un nmero entero correspondiente al puerto enlazado. Lanza un nuevo proceso que ejecuta la funcin init/1 encargada de abrir el puerto y pasar el control a la funcin loop/1 que se encarga de atender los paquetes que vayan llegando. En el ejemplo hemos usado un registro formado por los cuatro datos que nos enva cada paquete UDP adems del identificador:
{udp, Socket, IP, InPortNo, Packet}
120
Comunicaciones
Socket El manejador retornado por la funcin open/1 u open/2. IP La direccin IP en formato de tupla. InPortNo El puerto origen del paquete recibido. Packet El paquete recibido. Podemos abrir una consola de Erlang y lanzar el servidor. Para saber que el puerto se encuentra enlazado emplearemos la funcin inet:i/0 que nos proporciona informacin sobre las comunicaciones:
> udpsrv:start(2020). ok > inet:i(). Port [...] Recv Sent Owner Local Address [...] State Type 593 [...] 0 0 <0.34.0> *:2020 [...] BOUND DGRAM ok
La salida nos muestra el proceso (Owner) que tiene enlazado (State =:= BOUND) el puerto 2020 (Local) de tipo UDP (Type =:= DGRAM). Nos proporciona tambin otros datos estadsticos como los bytes recibidos (Recv) y enviados (Sent). Podemos escribir estas lneas en la consola de Erlang para probar el cdigo del servidor:
> {ok, Socket} = gen_udp:open(0). {ok,#Port<0.618>} 21> gen_udp:send(Socket, {127,0,0,1}, 2020, "hola mundo!"). ok recibido({127,0,0,1}): "hola mundo!"
La operacin de conexin se ha realizado abriendo una comunicacin con la funcin gen_udp:open/1 pasando como parmetro el puerto 0. El puerto 0 se usa para indicar que queremos que el sistema operativo seleccione un puerto automticamente por nosotros. Podemos recurrir a ejecutar de nuevo inet:i/0 para ver las conexiones abiertas y las estadsticas:
> inet:i(). Port [...] Recv Sent Owner Local Address [...] State Type 593 [...] 11 0 <0.34.0> *:2020 [...] BOUND DGRAM
121
Comunicaciones
618 ok
[...] 0
11
<0.38.0> *:33361
Ahora vemos dos lneas. La primera sigue siendo la del servidor y la segunda pertenece al cliente. Por los datos estadsticos vemos que la informacin que ha enviado el cliente (columna Sent) es la que ha recibido el servidor (columna Recv). Vamos a modificar el cdigo del servidor para que retorne al cliente una cadena de texto:
-module(udpsrv). -export([start/1, init/1, loop/1]). -record(udp, {socket, ip, port, msg}). start(Port) -> spawn(?MODULE, init, [Port]), ok. init(Port) -> {ok, Socket} = gen_udp:open(Port), loop(Socket). loop(Socket) -> receive stop -> gen_udp:close(Socket); Packet when is_record(Packet, udp) -> io:format("recibido(~p): ~p~n", [ Packet#udp.ip, Packet#udp.msg ]), #udp{ip=IP,port=Port} = Packet, gen_udp:send(Socket, IP, Port, "recibido"), loop(Socket) end.
Hemos empleado la funcin gen_udp:send/4 para enviar al remitente una respuesta enviando un texto fijo al cliente. Podemos probarlo en consola de la siguiente forma:
> udpsrv:start(2020). ok > {ok, Socket} = gen_udp:open(0, [{active, false}]). {ok,#Port<0.599>} > gen_udp:send(Socket, {127,0,0,1}, 2020, "hola mundo!"). ok recibido({127,0,0,1}): "hola mundo!" > gen_udp:recv(Socket, 1024). {ok,{{127,0,0,1},2020,"recibido"}}
El cliente recibe un paquete del servidor en el que le dice recibido. En la funcin gen_udp:open/2 hemos empleado el parmetro de opciones para indicar a gen_udp que no enve el paquete recibido al proceso. Al
122
Comunicaciones
Las opciones disponibles son iguales a las que se mostraron en la funcin gen_udp:open. Son las siguientes: list | binary Indica si se quiere recibir el paquete como una lista o un binario. El valor por defecto es list. {ip | ifaddr, ip_address()} La direccin IP en la que enlazar el puerto. inet | inet6 Emplea IPv4 o IPv6 para las conexiones. El valor por defecto es inet. {active, true | false | once} Indica si todos los mensajes recibidos por red sern pasados al proceso (true) o no (false) o slo la primera vez (once). El valor por defecto es true. {reuseaddr, true | false} Permite reutilizar el puerto. No lo bloquea. Por defecto esta opcin est deshabilitada.
Nota
Hay disponibles muchas opciones ms a las que no entraremos ya que son conceptos ms avanzados o muy especficos, con lo que salen del mbito de explicacin de este captulo. Si desea ms informacin sobre la funcin gen_tcp:listen/2 puede revisar 4 este enlace
4
http://www.erlang.org/doc/man/gen_tcp.html#listen-2
123
Comunicaciones
Si ponemos en escucha el puerto 2020 y volvemos a listar el estado de la red podemos ver que el proceso (en Owner) pasa a escuhar (State =:= LISTEN) en el puerto 2020 (en Local) y para el tipo TCP (Type =:= STREAM):
> {ok, Socket} = gen_tcp:listen(2020, [{reuseaddr, true}]). {ok,#Port<0.609>} 3> inet:i(). Port [...] Recv Sent Owner Local Address [...] State Type 609 [...] 0 0 <0.32.0> *:2020 [...] LISTEN STREAM ok
El siguiente paso ser mantener el proceso a la espera de una conexin entrante. Es el proceso que se llama aceptacin y se realiza con la funcin accept/1. Si lo ejecutamos en la consola de Erlang el sistema se quedar bloqueado a la espera de una conexin entrante:
> {ok, SockAceptado} = gen_tcp:accept(Socket).
La funcin emplea la variable Socket creada en listen/2 para esperar nuevas conexiones entrantes. Cuando una conexin entrante llega la funcin accept/1 acepta la conexin y finaliza su ejecucin retornando otra variable SockAceptado. Este socket se emplear para comunicarse con el cliente y obtener informacin del mismo. En otra consola podemos realizar la conexin entrante. Emplearemos la funcin connect/3 que tiene la siguiente sintaxis:
connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}
Como parmetros se indica la direccin IP a la que conectarse (Address en formato tupla), el puerto al que conectarse (Port) y las opciones para establecer la comunicacin (Options). Las opciones son las mismas que se describieron para la funcin listen/2. Abrimos otra consola y establecemos una comunicacin local entre ambos puntos escribiendo lo siguiente:
> {ok, Socket} = gen_tcp:connect({127,0,0,1}, 2020, []). {ok,#Port<0.599>} 2> inet:i(). Port [...] Recv Sent [...] Local Foreign State Type 599 [...] 0 0 [...] *:38139 *:2020 CONNECTED STREAM ok
La nueva consola de Erlang slo tiene constancia de una conexin existente que est en estado conectada (State) y va del puerto local 38139 al puerto 2020. Desde la nueva consola podemos enviar informacin al servidor a travs de la funcin send/2 la cual tiene la forma:
124
Comunicaciones
Como Socket emplearemos el que nos retorn la funcin connect/2. El Packet es la informacin que queremos enviar. El paquete permite formatos de lista de caracteres y lista binaria. Podemos ejecutarlo de la siguiente manera:
> gen_tcp:send(Socket, "hola mundo!").
En el servidor no vemos de momento nada por consola. Si ejecutamos 5 flush/0 aparecern los mensajes recibidos al proceso de la consola . Si agregamos a las opciones de la funcin listen/2 {active, false} el mensaje no es enviado al proceso sino que espera a que lo recibamos a travs del uso de la funcin recv/2. Esta funcin tiene la siguiente sintaxis:
recv(Socket, Length) -> {ok, Packet} | {error, Reason}
El Socket es el valor de retorno obtenido tras la ejecucin de la funcin accept/1. Length es el tamao mximo que se espera recibir del paquete. En caso de que el paquete sea mayor que el tamao una nueva ejecucin de recv/2 recoger el siguiente trozo de informacin. En caso de indicar un tamao cero se recibe todo el paquete sin limitacin de tamao. Para establecer el dilogo entre servidor y cliente slo necesitamos emplear las funciones send/2 y recv/2 para realizar la comunicacin bidireccional. En el momento en el que se desee finalizar la comunicacin por cualquiera de las dos partes emplearamos la funcin close/1.
Importante
El socket que se estableci para escucha se puede igualmente cerrar con close/1. Conviene cerrar los puertos antes de finalizar la ejecucin de los servidores para liberar los puertos empleados.
Recordemos que la consola es un proceso que se mantiene a la espera de recibir eventos. Todos los eventos que reciba la consola son interceptados por esta. Vase apndice B para ms informacin.
125
Comunicaciones
lo que hasta que la conexin entre cliente y servidor no se cierra ningn otro cliente puede ser atendido por el servidor. Este problema no se presenta en comunicaciones UDP. En TCP cada conexin servidora genera un socket de conexin con el cliente especfico. Para que el proceso de servidor no permanezca bloqueado atendiendo la conexin del primer cliente que conecte generamos un nuevo proceso para atender esa peticin entrante. De esta forma el proceso principal queda liberado para aceptar ms peticiones y generar nuevos procesos a medida que vayan llegando nuevas peticiones de clientes. Para que el nuevo socket generado sepa que tiene que enviar sus paquetes al nuevo proceso hay que emplear la funcin controlling_process/2, que tiene la forma:
controlling_process(Socket, Pid) -> ok | {error, Reason}
Escribiremos un pequeo mdulo para comprobar cmo funciona. Llamaremos al mdulo tcpsrv y agregaremos las funciones del servidor:
start(Port) -> spawn(fun() -> srv_init(Port) end). srv_init(Port) -> Opts = [{reuseaddr, true}, {active, false}], {ok, Socket} = gen_tcp:listen(Port, Opts), srv_loop(Socket). srv_loop(Socket) -> {ok, SockCli} = gen_tcp:accept(Socket), Pid = spawn(fun() -> worker_loop(SockCli) end), gen_tcp:controlling_process(SockCli, Pid), inet:setopts(SockCli, [{active, true}]), srv_loop(Socket). worker_loop(Socket) -> receive {tcp, Socket, Msg} -> io:format("Recibido ~p: ~p~n", [self(), Msg]), timer:sleep(5000), %% 5 segundos de espera Salida = io_lib:format("Eco: ~s", [Msg]), gen_tcp:send(Socket, Salida), worker_loop(Socket); {tcp_closed, Socket} -> io:format("Finalizado.~n"); Any -> io:format("Mensaje no reconocido: ~p~n", [Any]) end.
La funcin start/1 se encarga de lanzar en un proceso aparte la ejecucin de la funcin srv_init/1. El cometido de esta funcin inicializadora es establecer la escucha en un puerto TCP. El bucle de ejecucin para el servidor se basa en aceptar una conexin, generar un
126
Comunicaciones
nuevo proceso, pasarle el control de la conexin con el cliente al nuevo proceso y vuelta a empezar. La generacin del nuevo proceso tiene como funcin de bucle principal a worker_loop. Esta funcin integrara el protocolo a nivel de aplicacin para interactuar con el cliente. En nuestro ejemplo esperamos a recibir un mensaje y lo retornamos precedido de la palabra Eco. El siguiente cdigo es un ejemplo para probar el servidor:
cli_send(Port, Msg) -> Opts = [{active, true}], {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port, Opts), gen_tcp:send(Socket, Msg), receive {tcp, Socket, MsgSrv} -> io:format("Retornado ~p: ~p~n", [self(), MsgSrv]); Any -> io:format("Mensaje no reconocido: ~p~n", [Any]) end, gen_tcp:close(Socket).
La funcin cli_send/2 permite conectarse a un puerto local , enviar un mensaje y esperar por el retorno antes de finalizar la comunicacin. Hasta el momento todo funciona como la versin anterior. No obstante, hemos agregado en el servidor un retraso de 5 segundos que nos ayudar a ver la concurrencia en ejecuciones mltiples de cli_send/2. Lo podemos realizar con varias consolas o a travs de un cdigo como el siguiente:
cli_concurrent_send(Port) -> Send = fun(I) -> Text = io_lib:format("i=~p", [I]), spawn(tcpcli, cli_send, [Port, Text]) end, lists:foreach(Send, lists:seq(1,10)).
Este cdigo genera 10 procesos que ejecutan la funcin cli_send/2 enviando el mensaje i=I, siendo I el valor pasado por foreach/2 a cada uno de los procesos. La ejecucin muestra como todos los procesos llegan a recibir el mensaje en el servidor y quedan esperando por el resultado 5 segundos despus.
5. Ventajas de inet
Erlang no slo dispone de funciones para manejar las comunicaciones a nivel transporte. El mdulo inet a travs de la funcin setopts/2 provee
6
En este ejemplo no hemos empleado direcciones IP, por lo que se emplea por defecto la IP local o 127.0.0.1.
127
Comunicaciones
la capacidad de interpretar los paquetes recibidos a travs de TCP o UDP y enviarlos como mensaje al proceso ya procesados. Segn la documentacin de inet los formatos que procesa son: CORBA, ASN-1, SunRPC, FastCGI, Line, TPKT y HTTP.
7
Nota
La decodificacin la realiza nicamente a nivel de recepcin, el envo deberemos de componerlo nosotros mismos y enviarlo con la funcin de send/2 de gen_tcp.
Para construir nuestro propio servidor HTTP y aprovechar la caracterstica que nos provee inet slo tendramos que agregar la opcin a la funcin listen/2. Vamos a verlo con un ejemplo:
> Opts = [{reuseaddr, true}, {active, true}, {packet, http}], > {ok, Socket} = gen_tcp:listen(8080, Opts). {ok,#Port<0.604>} > {ok, SC} = gen_tcp:accept(Socket).
En este momento el sistema queda en espera de que llegue una peticin. Como hemos levantado un puerto TCP y le hemos configurado las caractersticas de HTTP, vamos a abrir un navegador con la siguiente URL:
http://localhost:8080/
Los mensajes recibidos por el sistema son tuplas que tienen como primer elemento http. Como en los casos de tcp el segundo parmetro es Socket. Como tercer parmetro puede aparecer otra tupla cuyo primer parmetro es:
7
http://www.erlang.org/doc/man/inet.html#setopts-2
128
Comunicaciones
http_request Si se trata de la primera lnea de peticin. Esta tupla tendr 4 campos: http_request, mtodo HTTP (GET, POST, PUT o DELETE entre otros), URI y versin HTTP en forma de tupla de dos elementos. Un ejemplo:
{http_request, 'GET', {abs_path,"/"},{1,1}}
http_header Las siguientes lneas a la peticin son las lneas de cabecera. Que se estructuran en una tupla de 5 campos: http_header, bit de cabecera, nombre de la cabecera, valor reservado (undefined) y valor de la cabecera. http_eoh Este dato se transmite en forma de tomo. Indica que la recepcin de cabeceras ha finalizado. A continuacin vemos un ejemplo completo. Presenta las peticiones recibidas por pantalla junto con su contenido:
-module(httpsrv). -export([start/1]). -define(RESP, "HTTP/1.1 200 OK Content-Length: 2 Content-Type: text/plain OK"). start(Port) -> spawn(fun() -> srv_init(Port) end). srv_init(Port) -> Opts = [{reuseaddr, true}, {active, false}, {packet, http}], {ok, Socket} = gen_tcp:listen(Port, Opts), srv_loop(Socket). srv_loop(Socket) -> {ok, SockCli} = gen_tcp:accept(Socket), Pid = spawn(fun() -> worker_loop(SockCli) end), gen_tcp:controlling_process(SockCli, Pid), inet:setopts(SockCli, [{active, true}]), srv_loop(Socket). worker_loop(Socket) -> receive {http, Socket, http_eoh} -> inet:setopts(Socket, [{packet, raw}]), worker_loop(Socket); {http, Socket, Header} -> io:format("Recibido ~p: ~p~n", [self(), Header]), worker_loop(Socket); {tcp, Socket, Msg} ->
129
Comunicaciones
io:format("Recibido ~p: ~p~n", [self(), Msg]), gen_tcp:send(Socket, ?RESP), gen_tcp:close(Socket); {tcp_closed, Socket} -> io:format("Finalizado.~n"), gen_tcp:close(Socket); Any -> io:format("Mensaje no reconocido: ~p~n", [Any]), gen_tcp:close(Socket) end.
El servidor es bastante simple ya que siempre retorna el mismo resultado. Si accedemos desde un navegador veremos en modo texto el mensaje OK. En la funcin worker_loop/1 cuando se recibe http_eoh se puede ver que se modifica el tipo de paquete para poder recibir el contenido. Adems vemos que se diferencian bien los mensajes que se reciben de tipo http de los que son de tipo tcp.
Nota
Si empleamos el parmetro {active, false} para emplear la funcin recv/2 en lugar de receive hay que tener presente que el retorno de la funcin recv/2 ser: {ok, HttpPacket}, mientras que el retorno de receive ser: {http, Socket, HttpPacket}.
130
1. Iniciar un Proyecto
A lo largo de los captulos hemos realizado la mayor parte del cdigo en la consola de Erlang y vimos la organizacin del cdigo interno y la realizacin de mdulos. An no hemos comentado la forma que debe tener nuestro espacio de trabajo, los directorios que es conveniente crear y la disposicin de los ficheros dentro de estos directorios. La herramienta rebar es la ms empleada entre las utilidades de terceros 1 del mundo Erlang. La empresa Basho es la desarrolladora principal de esta herramienta aunque cada da hay ms contribuidores al proyecto. Un proyecto en Erlang/OTP debe disponer de una estructura base como la siguiente: src Este directorio contendr el cdigo fuente. Todos los ficheros cuya extensin sea .erl. ebin Aqu se almacenarn los ficheros de tipo .beam, es decir la compilacin de nuestra aplicacin.
1
Basho Technologies es una empresa estadounidense que desarrolla la base de datos Riak.
131
Ecosistema Erlang
include Los ficheros que se almacenan en este directorio son los de tipo cabecera .hrl. priv Cuando el proyecto requiere de ficheros especficos para funcionar se introducen en este directorio ficheros como certificados, pginas HTML, hojas de estilo CSS o cdigos JavaScript entre otros.
Nota
Hay ms directorios por defecto para proyectos Erlang/OTP como c_src donde se alojan los ficheros de extensin escritos en C, test para los cdigos de pruebas de EUnit o CommonTest o deps es donde se bajan otros proyectos de terceros para incluir su cdigo dentro de nuestro proyecto.
Crearemos un proyecto que ilustre cmo organizar los ficheros del cdigo y cmo ejecutar ese mismo cdigo de forma autnoma. Para esta tarea nos ayudaremos de rebar. Creamos los tres directorios base y pasamos a instalar rebar.
Ahora solo nos falta copiar el script generado a una ruta visible por nuestro PATH. Normalmente como super usuario en sistemas de tipo 2 Unix en una ruta como /usr/bin, /usr/local/bin o /opt/local/ bin.
2
Sistemas Unix o tipo Unix como BSD, Linux, MacOS X u OpenSolaris entre otros.
132
Ecosistema Erlang
Nota
La utilidad rebar se encuentra tambin disponible para Windows 3 a travs del repositorio bifurcado (fork) de IRONkyle . Recomiendo que los proyectos iniciales y el aprendizaje se lleven a cabo en sistemas tipo Unix como MacOS o GNU/Linux por el motivo de que la mayora de soporte y desarrollos se realizan en estos sistemas.
https://github.com/IRONkyle/rebar
133
Ecosistema Erlang
{tcp_closed, Socket} -> error_logger:info_msg("Finalizado.~n"), gen_tcp:close(Socket); Any -> error_logger:info_msg("No reconocido: ~p~n", [Any]), gen_tcp:close(Socket) end.
El cdigo listado fue visto en la Seccin 4, Servidor TCP Concurrente del Captulo7, Comunicaciones. Solo agregaremos la llamada al mdulo fileserver. El segundo mdulo se encargar de buscar el fichero solicitado y retornarlo como texto identificando su tipo. El cdigo es el siguiente:
-module(fileserver). -export([send/1]). -define(RESP_404, <<"HTTP/1.1 404 Not Found Server: Erlang Web Server Connection: Close ">>). -define(RESP_200, <<"HTTP/1.1 200 OK Server: Erlang Web Server Connection: Close Content-type: ">>). send(Request) -> "/" ++ Path = proplists:get_value(path, Request, "/"), {ok, CWD} = file:get_cwd(), RealPath = filename:join(CWD, Path), case file:read_file(RealPath) of {ok, Content} -> Size = list_to_binary( io_lib:format("~p", [byte_size(Content)]) ), Type = mimetype(Path), << ?RESP_200/binary, Type/binary, "\nContent-lenght: ", Size/binary, "\r\n\r\n", Content/binary >>; {error, _} -> ?RESP_404 end. mimetype(File) -> case filename:extension(string:to_lower(File)) of ".png" -> <<"image/png">>; ".jpg" -> <<"image/jpeg">>; ".jpeg" -> <<"image/jpeg">>; ".zip" -> <<"application/zip">>; ".xml" -> <<"application/xml">>; ".css" -> <<"text/css">>; ".html" -> <<"text/html">>; ".htm" -> <<"text/html">>; ".js" -> <<"application/javascript">>; ".ico" -> <<"image/vnd.microsoft.icon">>;
134
Ecosistema Erlang
Con esto ya tenemos el cdigo preparado. Solos nos falta escribir la definicin necesaria para que rebar pueda identificar la aplicacin y construir el producto final. Crearemos el fichero en el directorio src con el nombre webserver.app.src:
{application, webserver {description {vsn , "1.0"}, , [
El nombre que tendr la aplicacin. Como descripcin se especifica un texto. Es deseable que no sea muy extenso. Se puede poner el nombre completo de la aplicacin. La versin de la aplicacin. Se puede especificar de la forma que se desee. Aplicaciones que deben de iniciarse antes de iniciar la nuestra. Dependencias.
Nota
Como versin en la lnea de vsn podemos emplear las palabras clave: git, hg, bzr, svn o {cmd, Cmd}. Las primeras indican al sistema que tome el tag o el nmero de revisin del sistema de control de versiones. La ltima indica que ejecute el comando contenido en Cmd para obtener la versin.
En la pgina de referencia de app podemos ver una lista ms completa y detallada de las opciones que permite el fichero para iniciar una aplicacin.
2. Compilar y Limpiar
Una vez que tenemos el directorio src creado podemos compilarlo todo ejecutando el comando: rebar compile. El comando rebar se encarga de crear el directorio ebin y depositar los ficheros beam dentro de l:
$ rebar compile ==> webserver_simple (compile) Compiled src/webserver.erl Compiled src/fileserver.erl
4
http://www.erlang.org/doc/man/app.html
135
Ecosistema Erlang
El directorio ebin contiene la compilacin de los cdigos listados en la seccin anterior. El fichero webserver.app.src se analiza y se completa para generar el fichero webserver.app dentro del directorio ebin. Si queremos ejecutar el cdigo, podemos iniciar la consola de la siguiente forma:
$ erl -sname webserver -pa ebin (webserver@bosqueviejo)1> webserver:start(8888). <0.39.0>
De esta forma tenemos el cdigo en ejecucin. Para eliminar estos ficheros generados ejecutamos el comando rebar clean.
Los comportamientos (behaviours) son un mecanismo de inversin de control (IoC) que posibilita la creacin de cdigo abstracto ms concreto para el usuario. Estos sern vistos en mayor profundidad en el Volumen II.
136
Ecosistema Erlang
-behaviour(application). -export([start/0, start/2, stop/1]). start() -> application:start(webserver). start(_StartType, _StartArgs) -> {ok, webserver:start(8888)}. stop(_State) -> ok.
Este mdulo dispone de tres funciones. Las funciones start/2 y stop/1 son requeridas por el comportamiento application, mientras que start/0 la emplearemos para la lnea de comandos. En el fichero webserver.app.src solo debemos de agregar una nueva lnea que indique qu mdulo se har cargo de las llamadas propias de la aplicacin para su inicio y fin:
{application, webserver, [ {description, "Erlang Web Server"}, {vsn, "1.0"}, {applications,[ kernel, stdlib, inets ]}, {mod, {webserver_app, []}} ]}.
Lnea que indica el mdulo de comportamiento application que se har cargo del inicio y parada de la aplicacin. En la consola agregaremos un par de argumentos ms :
$ erl -sname test -pa ebin -s inets -s webserver_app \ -noshell -detached 6
El comando se encarga de dar un nombre al nodo (-sname), decir donde se encuentra el cdigo que queremos lanzar (-pa), arrancar la aplicacin inets (-s) y la aplicacin webserver. Indicamos adems que no queremos que se ejecute una consola o shell (-noshell) y que se ejecute en segundo plano (-detached).
4. Dependencias
En Internet existen repositorios con miles de libreras para Erlang. 7 8 Los ms representativos son github.com y bitbucket . En estos sitios
6
Los argumentos usados para la lnea de comandos se pueden revisar en el Apndice B, La lnea de comandos. 7 https://github.com 8 https://bitbucket.org
137
Ecosistema Erlang
podemos encontrar libreras para conectar con MySQL, PostgreSQL, Memcached, o frameworks web como ChicagoBoss o Nitrogen, o frameworks para crear servidores web como cowboy o mochiweb entre otras muchas. La herramienta rebar posibilita a travs de su fichero de configuracin que podamos instalar en nuestro proyecto una librera externa con muy poco esfuerzo. El cdigo del fichero fileserver.erl muestra una funcin llamada mimetype/1. Esa funcin es insuficiente para cubrir todos los tipos posibles de ficheros que pudisemos utilizar en nuestra aplicacin. 9 Podemos emplear en su lugar la librera mimetypes . Para ello generaramos el fichero de configuracin rebar.config en la ruta raz del proyecto y con el siguiente contenido:
{deps, [ {mimetypes, ".*", {git, "https://github.com/spawngrid/mimetypes.git", "master"} } ]}.
La especificacin de la aplicacin la cambiamos tambin para agregar la nueva dependencia de la siguiente manera:
{application, webserver, [ {description, "Erlang Web Server"}, {vsn, "1.0"}, {applications,[ kernel, stdlib, inets, mimetypes ]}, {mod, {webserver_app, []}} ]}.
Agregamos en el fichero webserver_app.erl el lanzamiento de la aplicacin mimetypes para cumplir con la dependencia. La funcin start/0 quedara as:
-module(webserver_app). -behaviour(application). -export([start/0, start/2, stop/1]). start() -> application:start(mimetypes), application:start(webserver). start(_StartType, _StartArgs) -> {ok, webserver:start(8888)}.
https://github.com/spawngrid/mimetypes.git
138
Ecosistema Erlang
Por ltimo, cambiamos el cdigo escrito para que en lugar de tener el uso de nuestra funcin mimetype/1 emplee las que provee la librera:
-module(fileserver). -export([send/1]). -define(RESP_404, <<"HTTP/1.1 404 Not Found Server: Erlang Web Server Connection: Close ">>). -define(RESP_200, <<"HTTP/1.1 200 OK Server: Erlang Web Server Connection: Close Content-type: ">>). send(Request) -> "/" ++ Path = proplists:get_value(path, Request, "/"), {ok, CWD} = file:get_cwd(), RealPath = filename:join(CWD, Path), case file:read_file(RealPath) of {ok, Content} -> Size = list_to_binary( io_lib:format("~p", [byte_size(Content)]) ), [Type] = mimetypes:filename(Path), << ?RESP_200/binary, Type/binary, "\nContent-lenght: ", Size/binary, "\r\n\r\n", Content/binary >>; {error, _} -> ?RESP_404 end.
Antes de compilar debemos de lanzar el siguiente comando para descargar las dependencias que hemos indicado que necesitamos en nuestro proyecto:
$ rebar get-deps ==> webserver_deps (get-deps) Pulling mimetypes from {git, "https://github.com/spawngrid/mimetypes.git", "master"} Cloning into 'mimetypes'... ==> mimetypes (get-deps) $ rebar compile ==> mimetypes (compile) Compiled src/mimetypes_scan.xrl Compiled src/mimetypes_parse.yrl Compiled src/mimetypes_loader.erl Compiled src/mimetypes_scan.erl Compiled src/mimetypes_sup.erl Compiled src/mimetypes_app.erl
139
Ecosistema Erlang
Compiled src/mimetypes.erl Compiled src/mimetypes_parse.erl ==> webserver_deps (compile) Compiled src/webserver_app.erl Compiled src/webserver.erl Compiled src/fileserver.erl
Nota
El comando rebar get-deps se emplea para descargar las dependencias mientras que rebar del-deps se encarga de eliminarlas. Este ltimo comando es til para realizar una limpieza del proyecto junto con rebar clean:
$ rebar del-deps ==> mimetypes (delete-deps) ==> webserver (delete-deps) $ rebar clean ==> webserver (clean)
Para lanzar de nuevo la aplicacin agregaremos la ruta de las dependencias de esta forma:
$ erl -pa deps/*/ebin -pa ebin -sname test -s inets \ -s webserver_app -noshell -detached
Vemos al ejecutarlo que volvemos a tener el puerto 8888 disponible y los ficheros solicitados presentan ya unos tipos MIME ms precisos:
$ netstat -tln | grep 8888 tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN $ curl -i http://localhost:8888/rebar.config HTTP/1.1 200 OK Server: Erlang Web Server Connection: Close Content-type: application/octet-stream Content-lenght: 119 {deps, [ {mimetypes, ".*", {git, "https://github.com/spawngrid/mimetypes.git", "master"} } ]}.
5. Liberar y Desplegar
El desarrollo de software tiene su culminacin cuando el software puede ser instalado en sistemas en produccin. Liberar el cdigo consiste en dejar preparado el producto para su instalacin. Este debe de poderse empaquetar, construir y lanzar de forma fcil y simple. El despliegue consiste en el procedimiento de instalacin de este cdigo liberado.
140
Ecosistema Erlang
La herramienta rebar nos facilita la tarea de la liberacin generando una serie de scripts y ficheros de configuracin. Crearemos el directorio rel y dentro de l ejecutaremos el comando:
$ rebar create-node nodeid=webserver
Importante
El nombre que se d al nodo es preferible que no contenga guiones bajos. En el proceso de generacin de actualizaciones (appups y upgrades) podra generar errores.
Lo siguiente ser crear el directorio apps y un subdirectorio webserver. Dentro de webserver moveremos los directorios src y ebin.
Ajustaremos los ficheros rebar.config y el nuevo fichero dentro del directorio rel llamado reltool.config para adaptarlos a la nueva ubicacin del cdigo. En el fichero rebar.config basta con agregar esta lnea:
141
Ecosistema Erlang
{sub_dirs, ["apps/*"]}.
Nota
El directorio apps se emplea cuando se requieren escribir programas con varias aplicaciones. El comando rebar generate requiere que esta estructura exista para realizar la liberacin.
El fichero reltool.config es la configuracin que necesita el sistema denominado reltool para generar la liberacin. Las posibles entradas de configuracin que se pueden agregar al fichero son muy numerosas. Para profundizar ms el tema puedes visitar su pgina de documentacin 10 oficial . En este apartado recogeremos las ms importantes que se agregarn bajo la clave sys: {lib_dirs, [Dir1,Dir2..DirN]} El directorio (o directorios) que contiene las aplicaciones. Deberemos de agregarlo de la siguiente forma:
{lib_dirs, ["../apps", "../deps"]},
{rel, App, Vsn, [App1,App2..AppN]} Se especifica el nombre de la aplicacin (primer parmetro), la versin de la aplicacin (segundo parmetro) y las aplicaciones a ejecutar. El listado de aplicaciones debe contener las applicaciones en el orden en el que se deben de ir iniciando cuando se arranque el programa. Por lo tanto deberemos de agregar:
{rel, "webserver", "1.0", [ kernel, stdlib, sasl, inets, mimetypes, webserver ]},
{boot_rel, App} Se pueden crear tantos apartados rel como se necesiten. Uno de ellos debe marcarse por defecto con esta opcin. {profile, development | standalone | embedded} El perfil indica el nivel de restriccin a aplicar para la copia de dependencias, libreras o binarios entre otros. Hay tres perfiles de menos a ms restrictivo: development, standalone y embedded.
10
http://www.erlang.org/doc/man/reltool.html
142
Ecosistema Erlang
{incl_cond, include | exclude | derived } Indica el modo en que sern elegidas las aplicaciones que entrarn en la liberacin. Las opciones disponibles son: include Entran todas las aplicaciones menos la que explcitamente se indique que no entre. exclude Entran solo las aplicaciones que se indiquen de forma explcita que deban de entrar. derived Se incluyen las aplicaciones indicadas explcitamente y todas sus dependencias. {mod_cond, all | app | ebin | derived | none} Es como incl_cond pero a nivel de aplicacin. Las opciones que permite son: all Se incluyen todos los mdulos de cada aplicacin includa en la liberacin. app Se incluyen todos los mdulos listados en el fichero .app y 11 derivados . ebin Se incluyen todos los mdulos que estn en el directorio ebin 11 de la aplicacin y los derivados . derived Se incluyen los mdulos que estn siendo usados por los includos explcitamente. none No se incluye ninguno. {app, App, [ConfList]} Esta es la especificacin individual de cada aplicacin. Debe de existir al menos la principal. Como configuracin se puede indicar
11
Se refiere a todos los mdulos que no estn includos pero que reltool detecte que puedan ser utilizados.
143
Ecosistema Erlang
una entrada incl_cond que aplique solo sobre la aplicacin y opcionalmente otra mod_cond que acte solo sobre la aplicacin. En nuestro fichero pondremos nicamente:
{app, webserver, [{mod_cond, app}, {incl_cond, include}]}
Nota
Se pueden emplear filtros para agregar ciertos ficheros slo y segn qu nivel (archivo, sistema o aplicacin). No profundizaremos en este tema para no extendernos ms y porque este uso es ms una referencia que el lector puede encontrar fcilmente en la web oficial.
Otras entradas al mismo nivel de sys que se pueden encontrar son target_dir que indica el nombre del directorio donde se situar el resultado de la liberacin y overlay que contiene comandos adicionales a ejecutar durante la liberacin. Estos comandos adicionales son del tipo crear directorio (mkdir), copiar fichero (copy) o emplear una plantilla (template) a fusionar con un fichero de variables y generar el fichero que estar disponible en el despliegue. Aqu el fichero completo reltool.config:
{sys, [ {lib_dirs, ["../apps", "../deps"]}, {erts, [{mod_cond, derived}, {app_file, strip}]}, {app_file, strip}, {rel, "webserver", "1.0", [ kernel, stdlib, sasl, inets, mimetypes, webserver ]}, {rel, "start_clean", "", [ kernel, stdlib ]}, {boot_rel, "webserver"}, {profile, embedded}, {incl_cond, derived}, {mod_cond, derived}, {excl_archive_filters, [".*"]}, %% Do not archive built libs {excl_sys_filters, [ "^bin/.*", "^erts.*/bin/(dialyzer|typer)", "^erts.*/(doc|info|include|lib|man|src)"] }, {excl_app_filters, ["\.gitignore"]}, {app, webserver, [{mod_cond, app}, {incl_cond, include}]} ]}.
144
Ecosistema Erlang
{target_dir, "webserver"}. {overlay, [ {mkdir, "log/sasl"}, {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"}, {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"}, {copy, "files/webserver", "bin/webserver"}, {copy, "files/webserver.cmd", "bin/webserver.cmd"}, {copy, "files/start_erl.cmd", "bin/start_erl.cmd"}, {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"}, {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"}, {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"} ]}.
Obtenemos como resultado un directorio webserver en el que se encuentra la liberacin. El comando rebar nos proporciona en el directorio bin un script que nos permite lanzar el programa de varias formas: console En primer plano. Abre una consola y ejecuta todas las aplicaciones mientras vemos en pantalla los mensajes que imprime cada una de las aplicaciones al lanzarse. start / stop Estos comandos permiten iniciar y detener la aplicacin que se lanza en segundo plano. ping Hace un ping al nodo de la aplicacin. En caso de que est activo el nodo responder con un pong. attach Permite conectarse a una aplicacin ejecutndose en segundo plano. Si ejecutamos el comando start y despus ping podremos ver que el sistema responde sin problemas. Entramos a consola a travs de attach y podemos ver los mensajes de log que hayamos escrito en el fichero:
$ webserver/bin/webserver start
145
Ecosistema Erlang
/tmp/rel$ webserver/bin/webserver ping pong /tmp/rel$ webserver/bin/webserver attach Attaching to /tmp/rel/webserver/erlang.pipe.1 (^D to exit) (webserver@127.0.0.1)1>
Importante
Cuando nos conectamos a una aplicacin en ejecucin con attach debemos siempre salir con la pulsacin de las teclas Control+D. Si salimos interrumpiendo la consola la aplicacin se detendr.
El despliegue en un servidor u otro equipo informtico se realizar comprimiendo el resultado que se ha obtenido en webserver y descomprimirlo en el destino. El lanzamiento lo podemos agregar como script en los sistemas tipo Unix gracias a que respeta la forma de start y stop.
Nota
Una buena prctica es que cada vez que demos una versin como terminada, hagamos una compilacin en un fichero comprimido de la misma. Esto nos servir para poder transportar nuestro proyecto a produccin y crear actualizaciones.
6. Actualizando en Caliente
Una de las ventajas que reseamos de Erlang al principio es su capacidad para cambiar el cdigo en caliente sin necesidad de detener la ejecucin del programa. En la Seccin 8, Recarga de cdigo del Captulo 5, Procesos vimos cmo cargar cdigo en caliente. En esta seccin veremos cmo realiza esta accin rebar para cambiar el cdigo en caliente de todo un proyecto completo. Como ejemplo pensemos que necesitamos modificar el fichero fileserver.erl para que responda a una solicitud con una URI /help que retorne un texto personalizado de ayuda. Lo primero que haremos ser generar la versin 1.0 y modificar el nombre dentro del directorio rel de webserver a webserver_old. Hacemos los cambios oportunos en el fichero:
-module(fileserver). -export([send/1]). -define(RESP_404, <<"HTTP/1.1 404 Not Found
146
Ecosistema Erlang
Server: Erlang Web Server Connection: Close ">>). -define(RESP_200, <<"HTTP/1.1 200 OK Server: Erlang Web Server Connection: Close Content-type: ">>). -define(HELP_TEXT, <<"Texto de ayuda!">>). send(Request) -> case proplists:get_value(path, Request, "/") of "/help" -> Content = ?HELP_TEXT, Size = list_to_binary( integer_to_list(byte_size(Content)) ), << ?RESP_200/binary, "text/html", "\nContent-lenght: ", Size/binary, "\n\n", Content/binary >>; "/" ++ Path -> {ok, CWD} = file:get_cwd(), RealPath = filename:join(CWD, Path), case file:read_file(RealPath) of {ok, Content} -> Size = list_to_binary( integer_to_list(byte_size(Content)) ), Type = mimetype(Path), << ?RESP_200/binary, Type/binary, "\nContent-lenght: ", Size/binary, "\n\n", Content/binary >>; {error, _} -> ?RESP_404 end end. mimetype(File) -> case filename:extension(string:to_lower(File)) of ".png" -> <<"image/png">>; ".jpg" -> <<"image/jpeg">>; ".jpeg" -> <<"image/jpeg">>; ".zip" -> <<"application/zip">>; ".xml" -> <<"application/xml">>; ".css" -> <<"text/css">>; ".html" -> <<"text/html">>; ".htm" -> <<"text/html">>; ".js" -> <<"application/javascript">>; ".ico" -> <<"image/vnd.microsoft.icon">>; _ -> <<"text/plain">> end.
Tambin modificamos la versin dentro del fichero webserver.app.src para que refleje el cambio de versin y la versin en el fichero reltool.cfg para que sea 2.0 en lugar de 1.0.
147
Ecosistema Erlang
Tenemos dos directorios de nuestro proyecto. Uno con la versin 1.0 y otro con la versin 2.0. Es ahora cuando generamos los ficheros appup. Estos ficheros se generan por aplicacin y contienen informacin sobre los cambios que hay que realizar en caliente. Dejamos que rebar generate-appups nos genere todos los ficheros necesarios:
$ rebar generate-appups previous_release=webserver_old ==> rel (generate-appups) Generated appup for webserver Appup generation complete
El fichero generado para nuestra aplicacin es webserver.appup. Este fichero se crea en la ruta webserver/lib/webserver-2.0/ebin. Su forma es:
{"2.0", [ {"1.0", [ {load_module,fileserver} ]} ], [ {"1.0", [ {load_module,fileserver} ]} ]}.
La versin que va a ser instalada. Bloque que indica las acciones a llevar para pasar de la versin 1.0 a la 2.0. Cada opcin a llevar a cabo tendr forma de tupla. La accin load_module se refiere a la recarga del mdulo que se indica (en este caso fileserver). Bloque de las acciones a llevar a cabo en caso de querer realizar una marcha atrs de la versin 2.0 a la versin 1.0.
148
Ecosistema Erlang
Nota
El fichero appup permite muchos ms comandos. Si los cambios han sido ms significativos como la agregacin o eliminacin de mdulos se pueden emplear otros comandos como add_module y/o delete_module. El sistema tambin permite trazar la dependencia de mdulos y el orden en el que se deben de ir cargando a travs de las opciones PrePurge, PostPurge y DepMods de las formas completas de las tuplas de comandos que 12 pueden verse en la web oficial de appup . Por ejemplo:
{add_module, filesystem}, {add_module, ftp}, {load_module, webserver}, {code_change, [{webserver, undefined}]}, {delete_module, fileserver}
Esta forma se aplicara cuando cambiamos fileserver.erl por otros mdulos diferentes. Primero cargamos los nuevos, recargamos el manejador, enviamos el cdigo de cambio para 13 el manejador y eliminamos el mdulo antiguo. El soporte para system_code_change/4 debe de existir en el mdulo webserver.
Ahora generamos el paquete de la nueva versin. Para ello utilizamos el comando rebar generate-upgrade de la siguiente forma:
$ rebar generate-upgrade previous_release=webserver_old ==> rel (generate-upgrade) webserver_2.0 upgrade package created
El resultado ser un fichero webserver_2.0.tar.gz. Este fichero contiene los binarios del programa para ejecutarse en produccin, as como la informacin de cada aplicacin y los ficheros para recargar cada una de manera adecuada. El fichero debe de copiarse en la ruta webserver/releases. Si hemos lanzado el cdigo antiguo que est en webserver_old, copiamos dentro de su directorio releases este fichero. Ejecutamos:
$ cp webserver_2.0.tar.gz webserver_old/releases $ webserver_old/bin/webserver upgrade webserver_2.0 Unpacked Release "2.0" Installed Release "2.0" Made Release "2.0" Permanent
12 13
http://www.erlang.org/doc/man/appup.html http://www.erlang.org/doc/man/sys.html#Mod:system_code_change-4
149
Ecosistema Erlang
Importante
Para que el sistema no falle habr que revisar la configuracin del nombre de nodo en el fichero vm.args dentro del directorio files antes de realizar la generacin del producto final. Es recomendable emplear sname y solo indicar el nombre del nodo eliminando el nombre de la mquina.
Si accedemos mediante navegador web a la URI /help veremos que el cdigo se ha actualizado y muestra ahora el nuevo texto de ayuda que hemos agregado.
7. Guiones en Erlang
Los guiones son un tipo de programacin en la que se desarrolla un cdigo de forma rpida para servir de guin a una tarea automatizada. Normalmente por un administrador de sistemas. Dentro de las tareas ms usuales de los guiones se encuentran el lanzamiento, monitorizacin y parada de aplicaciones. Erlang permite la realizacin de este tipo de guiones a travs de escript. El comando escript interpreta de forma rpida cdigos Erlang que deben constar de una funcin main/1. Por ejemplo:
#!/usr/bin/env escript main([]) -> io:format("Usage: fact <number>~n~n"), 1; main([NumberTxt]) -> try Number = list_to_integer(NumberTxt), io:format("~p! = ~p~n", [Number, fact(Number)]), 0 catch error:badarg -> main([]) end. fact(1) -> 1; fact(N) -> N * fact(N-1).
La funcin main/1 siempre recibe una lista de cadenas de texto. Si no hay argumentos la lista est vaca.
Nota
La primera lnea es conocida como shebang o hashbang. Indica el comando con el que hay que ejecutar ese guin.
150
Ecosistema Erlang
En el cdigo no existe declaracin de mdulo ni exportacin de funciones. An sin estar compilado puede hacer uso de cualquier librera de Erlang adems de todos los ejemplos de cdigo vistos anteriormente. Podemos probarlo as:
$ chmod +x fact $ ./fact Usage: fact <number> $ ./fact a Usage: fact <number> $ ./fact 12 12! = 479001600
El comando rebar escriptize se encarga tomar todos los mdulos compilados de un proyecto e introducirlos en un fichero binario y ejecutable. Esto permite la distribucin fcil de ese script y su instalacin en el sistema de ficheros. Si creamos una estructura de aplicacin normal con su directorio src con el fichero fact.erl que empleamos cambindolo de esta forma:
-module(fact). -export([main/1]). main([]) -> io:format("Usage: fact <number>~n~n"), 1; main([NumberTxt]) -> try Number = list_to_integer(NumberTxt), io:format("~p! = ~p~n", [Number, fact(Number)]), 0 catch error:badarg -> main([]) end. fact(1) -> 1; fact(N) -> N * fact(N-1).
14
Aunque hay versiones de rebar en las que la ayuda no lo muestra existe y funciona en esas mismas funciones.
151
Ecosistema Erlang
Si realizamos la prueba que hicimos con el guin anterior veremos que se comporta exactamente igual. Esta forma tiene la ventaja de que se pueden incrustar ms mdulos, dependencias y que el cdigo se encuentra ya compilado por lo que es ms rpido que en el ejemplo anterior.
8. El camino a OTP
Como ya avanc en la introduccin este libro consta de dos partes. En esta primera parte hemos visto todo lo necesario para conocer el lenguaje y el funcionamiento de la mquina virtual de Erlang. Hemos repasado cmo trabajar con los proyectos. Hemos formado nuestra mente a un nuevo conocimiento y a una nueva forma de hacer las cosas. Sin embargo en los proyectos profesionales de Erlang se emplea y con mucha frecuencia OTP. Lo aprendido a lo largo de estas pginas constituye una base de conocimiento y una forma de trabajo con el lenguaje, pero ese conocimiento debe ser ampliado a travs del estudio de OTP para brindar mejores soluciones al cdigo escrito. Espero que el libro haya resultado til, ameno y que la curiosidad despertada por Erlang haya sido satisfecha e incluso las ganas de seguir aprendiendo con el siguiente volumen de este libro. Hasta entonces, un saludo y suerte con el cdigo.
152
Apndices
1. Instalacin en Windows
Aunque siempre recomiendo GNU/Linux o incluso algn sistema BSD para programar y desarrollar software, las preferencias de cada uno son distintas y hay muchos usuarios y programadores que prefieren Windows a cualquier otro sistema operativo. La descarga para Windows se puede realizar desde la web de descargas de la pgina oficial de Erlang. Entre los paquetes que hay para descargar 3 se puede encontrar Windows Binary File . Se trata de un instalador que nos guiar paso a paso en la instalacin. La instalacin en estos sistemas se divide en varios pasos. Se seleccionan los paquetes a instalar y la ruta:
2
1 2 3
http://erlang.org/ http://www.erlang.org/download.html Tambin se encuentra la versin de 64 bits para los que tengan sistemas Windows de 64 bits.
154
Instalacin de Erlang
Nota
La instalacin de la versin R12B02 requiere de la instalacin de unas DLLs que son propiedad de Microsoft. El instalador inicia un proceso de instalacin para estas libreras en las que habr que aceptar las licencias y acuerdos de uso de las propias libreras. En un futuro se plantea la eliminacin de estas libreras en favor de otras de mingw (una versin de GCC para Windows) que nos permitirn saltar esta parte.
Seguimos con la instalacin hasta que el instalador nos informe de que ha finalizado con xito. En el men de inicio veremos que se ha creado un nuevo grupo de programas. Podemos lanzar el que tiene como ttulo Erlang con el logotipo del lenguaje a su lado para que se muestre la siguiente pantalla:
Ahora ya tenemos lista la consola de Erlang. Podemos tomar cualquier ejemplo del libro para probar su funcionamiento.
155
Instalacin de Erlang
hace mucho tiempo las versiones de Erlang disponibles pueden ser algo antiguas.
La compilacin requiere que se disponga en el sistema de un compilador y las libreras en las que se basa Erlang. El comando configure nos dar pistas sobre las libreras que haya que instalar.
Importante
Sistemas como Ubuntu no disponen acceso directo como usuario root. En su lugar se debe de acceder a root a travs del comando sudo. Para realizar la accin anterior sin que surjan problemas, deberemos de ejecutar antes: sudo su.
156
Instalacin de Erlang
3. Otros sistemas
La empresa Erlang Solutions provee paquetes de instalacin para otros sistemas como MacOS X. En este sistema podemos optar por instalar este 4 paquete o por la instalacin desde otros sistemas como MacPorts . En sistemas como OpenSolaris o BSD (FreeBSD, OpenBSD o NetBSD) la solucin ms comn es instalar desde cdigo fuente tal y como se coment en la seccin Seccin2.2, Compilando el Cdigo Fuente .
http://www.macports.org/
157
Importante
Las funciones que se listan a continuacin estn disponibles solo en la lnea de comandos, no es posible emplearlos en el cdigo de un programa convencional.
1. Registros
Los registros se comentaron en la Seccin1.6, Registros del Captulo2, El lenguaje. En la consola se pueden gestionar los registros a travs de las siguientes funciones: rd(R,D) Define un registro en la lnea de comandos:
> rd(state, {hits, miss, error}).
rl() / rl(R) Muestra todos los registros definidos en la lnea de comandos en el primer caso y solo el registro pasado como parmetro en el segundo caso. La definicin se muestra como se escribira dentro de un fichero de cdigo. rf() / rf(R) Elimina la definicin de los registros cargados. La primera forma elimina todos los registros mientras que la segunda solo elimina el registro pasado como parmetro R.
158
La lnea de comandos
rr(Modulo) / rr(Wildcard) / rr(MoW,R) / rr(MoW,R,O) Carga los mdulos de uno o varios ficheros. Los ficheros se pueden indicar mediante el nombre de un mdulo (vase m()) o el nombre de un fichero o varios con el uso de comodines (wildcard). Se puede agregar un segundo parmetro que indique el registro que se desea cargar y un tercer parmetro que se usar como conjunto 1 de opciones .
2. Mdulos
Indicaremos todos los comandos referentes a la compilacin, carga e informacin para los mdulos: c(FoM) Compila un fichero pasando su nombre como parmetro. El nombre proporcionado ser un tomo con el nombre del mdulo o una cadena que indique el nombre del fichero, opcionalmente con su ruta. l(M) Permite cargar un mdulo. Conviene recordar lo ya mencionado sobre la carga de mdulos en la Seccin8, Recarga de cdigo del Captulo5, Procesos. m() / m(M) Muestra todos los mdulos cargados en memoria en el primer caso e informacin detallada del mdulo cargado en el segundo caso. Se muestra informacin como la fecha y hora de compilacin, la ruta de dnde se encuentra el mdulo en el sistema de ficheros, las funciones que exporta y las opciones de compilacin. lc([F]) Lista de ficheros a compilar. nl(M) Carga el mdulo indicado en todos los nodos conectados. nc(FoM) Compila y carga el mdulo o fichero en todos los nodos conectados. y(F) Genera un analizador Yecc, el fichero pasado como parmetro debe de ser un fichero con sintaxis vlida para Yecc.
1
Las opciones que se pueden usar con rr/3 son las mismas que se pueden emplear para la compilacin.
159
La lnea de comandos
3. Variables
En la lnea de comandos se pueden emplear variables. Estas variables tienen el comportamiento de nica asignacin igual que el cdigo que podemos escribir en cualquier mdulo. Las siguientes funciones nos permiten gestionar estas variables: b() Muestra todas las variables empleadas o enlazadas (binding) a un valor en la lnea de comandos. f() / f(X) Indica a la lnea de comandos que olvide (forget) todas las variables o solo la indicada como parmetro.
4. Histrico
La consola dispone de un histrico que nos permite repetir comandos ya utilizados en la consola. El histrico es configurable y contendr los ltimos comandos tecleados. El smbolo del sistema (o prompt) nos indicar el nmero de orden que estamos ejecutando. Adems de los comandos, la consola de Erlang tambin almacena los ltimos resultados. El nmero de resultados almacenados tambin es configurable. Estas son las funciones que pueden emplearse: e(N) Repite el comando con orden N segn el smbolo de sistema de la consola. h() Muestra el histrico de comandos ejecutados. history(N) Configura el nmero de entradas que sern almacenadas como histrico. results(N) Configura el nmero de resultados que sern almacenados como histrico.
160
La lnea de comandos
v(N) Obtiene el resultado de la lnea correspondiente pasada como parmetro. A diferencia de e(N) el comando no se vuelve a ejecutar, solo se muestra el resultado del comando N ejecutado anteriormente.
5. Procesos
Estas son funciones rpidas y de gestin sobre los temas que ya se revisaron en el Captulo5, Procesos: bt(Pid) Obtiene el trazado de pila del proceso en ejecucin. flush() Muestra todos los mensajes enviados al proceso de la consola. i(X,Y,Z) Muestra informacin de un proceso dando sus nmeros como argumentos separados de la funcin. La informacin obtenida es el estado de ejecucin del proceso, procesos a los que est enlazado, la cola de mensajes, el diccionario del proceso y memoria utilizada entre otras opciones ms. pid(X,Y,Z) Obtiene el tipo de dato PID de los nmeros dados. regs() / nregs() Lista todos los procesos registrados (con nombre) en el nodo actual o en todos los nodos conectados respectivamente. catch_exception(B) Cada ejecucin se realiza mediante un evaluador. Cuando se lanza una excepcin el evaluador es regenerado por el proceso de la consola. Esto provoca que se pierdan tablas ETS entre otras cosas. Si ejecutamos esta funcin con true el evaluador captura la excepcin y no muere. i() / ni() Muestra todos los procesos del nodo o de todos los nodos conectados respectivamente.
161
La lnea de comandos
6. Directorio de trabajo
En cualquier momento podemos modificar el directorio de trabajo dentro de la consola. Las siguientes funciones nos ayudan en esta y otras tareas relacionadas: cd(Dir) Cambia el directorio de trabajo. Se indica una lista de caracteres con la ruta relativa o absoluta para el cambio. ls() / ls(Dir) Lista el directorio actual u otro indicado como parmetro de forma relativa o absoluta a travs de una lista de caracteres. pwd() Imprime el directorio de trabajo actual.
7. Modo JCL
Cuando se presiona la combinacin de teclas Control+G se accede a una nueva consola. Esta consola es denominada JCL (Job Control Mode o modo de control de trabajos). Este modo nos permite lanzar una nueva consola, conectarnos a una consola remota, detener una consola en ejecucin o cambiar de una a otra consola.
Importante
Cada trabajo que se lanza es una consola (shell). Este modo nos permite gestionar estas consolas. Cada nodo puede tener tantas consolas como se quiera.
Estos son los comandos que podemos emplear en este modo: c [nn] Conectar a una consola. Si no se especifica un nmero vuelve al actual. i [nn] Detiene la consola actual o la que corresponda al nmero que se indique como argumento. Es til cuando se quiere interrumpir un bucle infinito sin perder las variables empleadas. k [nn] Mata la consola actual o la que corresponda al nmero que se indique como argumento.
162
La lnea de comandos
j Lista las consolas en ejecucin. La consola actual se indicar con un asterisco (*). s [shell] Inicia una consola nueva. Si se indica el nombre de un mdulo como argumento se intentar lanzar un proceso con ese mdulo como consola alternativa. r [node [shell]] Indica que deseamos crear una consola en un nodo al que se tiene conexin. Se lanza una consola en ese nodo y queda visible en el listado de consolas. Se puede indicar tambin una consola alternativa en caso de disponer de ella. q Finaliza la ejecucin del nodo Erlang en el que estemos ejecutando el modo JCL.
8. Salir de la consola
Para salir de la consola hay varias formas. Se puede salir presionando dos veces la combinacin de teclas Control+C, ejecutando la funcin de consola q() o a travs del modo JCL y su comando q.
163
1. Barra de herramientas
Para facilitar la tarea de acceder al conjunto de herramientas grficas disponemos de toolbar. Esta aplicacin nos proporciona una ventana con botones de acceso directo a las herramientas de la interfaz grfica de Erlang. La podemos lanzar de la siguiente manera:
> toolbar:start().
Los cuatro botones que se pueden observar en la imagen son (de izquierda a derecha): tv Table Visualizer o visor de tablas. Se emplea para poder visualizar el contenido de las tablas ETS y las que maneja la base de datos Mnesia. pman Process Manager o administrador de procesos. Es la versin grfica de lo que conseguimos con las funciones de consola i/0, i/1 y otras funciones como exit/1 integradas en una nica interfaz. debugger El depurador nos permite seguir la ejecucin de un cdigo en la ventana de proceso y revisar los datos de sus variables en ese momento.
164
Herramientas grficas
appmon Application Monitor o monitor de aplicaciones. Permite ver la carga que supone en el nodo la aplicacin y el rbol de dependencia de procesos entre otras opciones.
Nota
En los mens podemos ver opciones como Add GS contributions que agrega otros cuatro botones extra: el juego bonk, mandelbrot que es un generador de fractales, el juego othello y el juego Cols. El cdigo fuente de estas aplicaciones est disponible junto con el cdigo fuente de Erlang, en el directorio:
otp_src_R15B02/lib/gs/contribs
2. Monitor de aplicaciones
El monitor de aplicaciones nos proporciona informacin sobre las aplicaciones ejecutadas en la mquina virtual de Erlang. El concepto de aplicacin proviene de OTP y se coment en el Captulo 8, Ecosistema Erlang. La aplicacin al lanzarse genera un proceso principal que puede estar enlazado con otros. Esta relacin de procesos es la que se monitoriza bajo el nombre propio de cada aplicacin que se ejecuta. En el grfico adjunto se puede ver el nombre del nodo (con fondo negro) del que cuelgan todas las aplicaciones que se han sido lanzadas.
Estas aplicaciones son botones que si se presionan nos muestran una jerarqua de procesos. Cada proceso se identifica con su nombre registrado o con su PID en caso de no disponer de nombre. Sobre cada proceso podemos ejecutar una serie de acciones que se representan con los botones superiores que se pueden ver en la ventana:
165
Herramientas grficas
La barra superior de botones es como una barra de herramientas. Siempre hay un botn que aparece como marcado indicando as la opcin que se har sobre cualquier proceso cuando se haga clic sobre l. Las acciones son: Info Se abrir una nueva ventana que mostrar la informacin del proceso. Send Permite enviar un mensaje al proceso. Es equivalente a:
Pid ! Mensaje
Trace Cada mensaje recibido al proceso marcado es impreso por la consola. Es equivalente a la funcin erlang:trace/3. Kill Enva la seal de finalizacin al proceso. Equivalente a exit/1. El men de la ventana principal dispone adems de otras opciones en su men Actions como: Reboot, Restart, Stop y Ping. Estas opciones son tiles cuando se emplea la herramienta con otros nodos, ya que permite reiniciar estos nodos y cuando se vean como desconectados, hacerles ping para volver a conectarlos.
166
Herramientas grficas
Nota
El monitor de aplicaciones no solo puede visualizar el estado de las aplicaciones del nodo en el que fue lanzado, sino que tambin puede ver el estado de las aplicaciones de otros nodos a los que se encuentre conectado. Esto es posible a travs del men Nodes, donde se listarn todos los nodos a los que est conectada la mquina virtual de Erlang.
3. Gestor de procesos
Una forma simple de gestionar los procesos que se ejecutan en la mquina virtual de Erlang es a travs de su gestor de procesos grfico. El gestor de procesos nos permite visualizar de forma rpida y en tiempo real los procesos que se estn ejecutando en la mquina virtual de Erlang su PID, nombre registrado (si tienen), la funcin actual que estn ejecutando, los mensajes que tienen pendientes en el buon, las reducciones aplicadas y el tamao que ocupa el proceso.
Esta interfaz permite activar el trazado de procesos en una ventana aparte, visualizando en ella los mensajes entrantes al proceso que se est trazando. Para trazar un proceso solo hay que seleccionarlo y a travs del men Trace seleccionar la opcin Trace selected process.
Nota
El gestor de procesos no solo puede visualizar y gestionar los procesos del nodo en el que fue lanzado, sino que tambin puede ver y gestionar los procesos de otros nodos a los que se encuentre conectado. Esto es posible a travs del men Nodes, donde se listarn todos los nodos a los que est conectada la mquina virtual de Erlang.
167
Herramientas grficas
4. Visor de tablas
Las tablas ETS y las que se crean con la base de datos de Mnesia estn disponibles para su visualizacin a travs de esta interfaz. Por defecto nos muestra un listado de las tablas ETS que hay creadas en el nodo. La informacin que se muestra en la tabla principal es: nombre de la tabla, identificador de la tabla, PID del propietario de la tabla, nombre registrado del proceso propietario (si tiene) y tamao de la tabla (en nmero de entradas).
A travs del men View se puede conmutar entre la visualizacin de las tablas ETS y las tablas Mnesia. En el men Options hay opciones referentes a la visualizacin de las tablas en el listado: refrescar, ver tablas del sistema o no legibles son algunas de las opciones. Si hacemos doble clic sobre cualquier entrada de la tabla principal, se abrir una segunda ventana en la que se mostrar el contenido de la tabla ETS seleccionada. Se visualiza como si fuese una hoja de clculo y se marca sobre las columnas con el smbolo de una llave cul es la clave primaria de la tabla. Esta es una ventana de visualizacin y edicin por lo que podemos seleccionar las entradas de la tabla y editarlas o eliminarlas a travs de las opciones disponibles en el men Edit.
Nota
El visualizador de tablas no solo puede visualizar y gestionar las tablas ETS y Mnesia del nodo en el que fue lanzado, sino que tambin puede hacer lo mismo con otros nodos a los que se encuentre conectado. Esto es posible a travs del men File, opcin Nodes..., donde se listarn todos los nodos a los que est conectada la mquina virtual de Erlang.
168
Herramientas grficas
5. Observer
El programa observer es una unificacin de pman, tv y appmon adems de otras caractersticas ms en un entorno wxWindow mejorado con respecto a los anteriores. Podemos lanzar este programa de la siguiente manera:
> observer:start().
La ventana abierta dispone de un conjunto de pestaas o lengetas que disponen de todas las funcionalidades del conjunto de programas vistos en las secciones anteriores: System Ofrece informacin del sistema: versin y arquitectura de la mquina virtual de Erlang, CPUs e hilos en ejecucin, uso de la memoria y estadsticas de uso. Load Charts Se presentan tres grficos en tiempo real: utilizacin del programador de tareas (equivalente a la carga del procesador), uso de la memoria y uso de la Entrada/Salida. Applications Ofrece la misma informacin que appmon. Visualiza el listado de aplicaciones a la izquierda y el rbol de procesos a la derecha, previa seleccin de una de las aplicaciones. Processes Listado de procesos tal y como se presentaban tambin en pman. Se muestra una tabla con los procesos activos y su informacin: PID, nombre o funcin inicial, reducciones, memoria, mensajes encolados y funcin en ejecucin actualmente. Table Viewer Lista las tablas ETS y Mnesia como lo hace la aplicacin tv. A diferencia de tv, observer no permite la creacin de nuevas tablas ETS aunque s permite modificar y/o eliminar entradas de las tablas existentes. Trace Overview Tanto la aplicacin appmon como pman permiten trazar procesos. La aplicacin observer unifica esto en una sola pestaa cambiando la forma en la que realizar las trazas de los procesos.
169
Herramientas grficas
Nota
Observer no solo puede actuar en el nodo en el que es lanzado, sino que tambin puede interactuar con otros nodos a los que se encuentre conectado. Esto es posible a travs del men Nodes, donde se listarn todos los nodos a los que est conectada la mquina virtual de Erlang adems de dar la posibilidad de conectar con otros nodos que no se encuentren en el listado.
6. Depurador
Esta es quizs la herramienta ms importante y que mejor se debera de aprender a utilizar para poder analizar el cdigo realizado de una forma ms cercana al dato. Aunque las trazas sern suficientes en algunos casos, siempre es mejor depurar un cdigo que nos origina un error que no conseguimos entender bien que abarcar el problema al modo pruebaensayo-error. El depurador se lanza desde consola as:
> debugger:start(local).
170
Herramientas grficas
La forma ms sencilla de emplear el depurador es a travs del men Module, opcin Interpret... cargar un fichero de cdigo fuente. En el propio men Module debe de agregarse despus una opcin con el nombre del mdulo cargado. Tras esto, presionamos los tres cuadros de verificacin visibles bajo el cuadro de la ventana donde aparece el nombre del mdulo: First Call, On Break y On Exit. Con esto conseguimos que el depurador se active cuando se suceda cualquiera de estos tres eventos sobre el mdulo. En la consola de Erlang ejecutamos una funcin del mdulo. Vemos que se abrir otra ventana de depuracin con el cdigo en la parte principal, una botonera en la parte media con los botones: Next, Step, Finish, Where, Up y Down.
171
Herramientas grficas
La parte inferior de la ventana muestra un listado de las variables del contexto de la funcin que se est depurando a la derecha. En la parte izquierda hay un evaluador que permite escribir expresiones simples para comprobacin de datos. Durante la ejecucin la ventana principal mostrar la informacin de los procesos que hay en ejecucin, la llamada que los inici, el nombre del proceso, el estado del proceso (ejecucin, ocioso u otro) e informacin sobre la ejecucin del proceso.
Nota
El depurador se puede lanzar en dos modos: local o global. Por defecto se lanza de modo global, por lo que cualquier mdulo que se interprete ser mostrado en la ventana del monitor. No es aconsejable lanzar el depurador en un cluster de ms de dos nodos, ya que la ejecucin concurrente de un mismo mdulo en varios nodos al mismo tiempo podra llevar a un funcionamiento inconsistente.
Tomando ejemplos de los ltimos captulos podemos hacer pruebas de ejecucin del cdigo, probar opciones de compilacin, ver los procesos, la evaluacin de expresiones, el contenido de las variables y otros aspectos que nos ayuden a aprender a utilizar bien esta herramienta. Para ms informacin sobre la misma: http://www.erlang.org/doc/apps/debugger/debugger_chapter.html
172
173