Full text
Interfaces gráficas de usuario con Elixir Graphical user interfaces with Elixir Ignacio Nicolás López Director: Manuel Montenegro Montes Trabajo de Fin de Grado en Ingeniería Informática Curso 2019 - 2020 1
2
Resumen 5 Palabras clave 5 Abstract 6 Keywords 6 I Introducción 7 Antecedentes 7 ¿Qué es Elixir? 7 Motivación 7 Objetivos 8 Plan de trabajo 8 II Introduction 11 Background 11 What is Elixir? 11 Motivation 11 Objectives 12 Work plan 12 III Herramientas y tecnologías 15 Erlang 15 Elixir 15 wxWidgets 15 Elm 15 WxHelper 16 Git 16 Visual Studio Code 16 Google Drive 17 Documentos de Google 17 Google Meet 17 IV Arquitectura básica 18 V Arquitectura general de la librería 25 Los árboles de diferencia 25 Módulo Builder 281 Módulo Comparator 31 Módulo GUIUpdater 34 Módulo Elixir_elm 35 3
VI Conclusiones y trabajo futuro 37 Conclusiones y objetivos 37 Trabajo futuro 38 VII Conclusions and future work 39 Conclusions and objectives 39 Future work 40 Bibliografía 42 Libros: 42 Páginas web: 42 4
Resumen En este Trabajo de Fin de Grado se desarrolla ElixirElm, una biblioteca de desarrollo de interfaces gráficas de usuario para el lenguaje Elixir. El desarrollo de interfaces gráficas en Elixir se realiza de una forma demasiado alejada de un paradigma funcional, por lo que resulta lógico buscar alguna alternativa que elimine esta falta de naturalidad en el desarrollo. Así pues, ElixirElm tiene como propósito otorgar a los programadores de aplicaciones gráficas en Elixir una capa de abstracción que solucione esta problemática. El objetivo de esta capa de abstracción es mantener el paradigma funcional de programación que tiene Elixir, pero que la librería WxErlang, utilizada para el desarrollo de interfaces en Elixir, contradice forzando su uso mediante un paradigma imperativo “orientado a objetos”. El nombre de la biblioteca es fruto de juntar el nombre del lenguaje Elixir, lenguaje en el que está programada y lenguaje objetivo de la librería, y Elm, otro lenguaje funcional de programación de aplicaciones web, cuya arquitectura es el objetivo de la capa de abstracción que otorga ElixirElm. ElixirElm permitirá a los programadores que hagan uso de ella programar interfaces gráficas de propósito general con un conjunto básico de componentes visuales, manteniendo la coherencia con el resto de su programa. Nuestra biblioteca para la construcción de interfaces de usuario hará uso internamente de la biblioteca WxErlang, pero el programador tan solo tendrá que interactuar con ElixirElm. Palabras clave Elixir, WxWidgets, WxErlang, Elm, GUI, biblioteca, librería, interfaz gráfica, lenguaje funcional. 5
Abstract In this Final Degree Project, ElixirElm, a library for developing graphical user interfaces, is developed. The development of graphical interfaces in Elixir is done in a way that does not fit very well in a functional language, seeming only logic to try and find any alternative that bypasses that lack of naturalness in the development. Said that, ElixirElm has as purpose to grant the programmers of graphical application in Elixir an abstraction layer that gives a solution to this problematic. The goal of this abstraction layer is to maintain the functional programming paradigm of Elixir, that the library WxErlang, used for the building of the interfaces in Elixir, contradicts by forcing its imperative “object oriented” paradigm upon the programmers. The name of the library comes from joining the name of the programming language Elixir: language in which the library is programmed and the target of the library as well, and the name of the programming language of Elm: another functional language for web applications, whose syntax is the one to achieve by the abstraction layer of ElixirElm. ElixirElm will allow its programmers to program graphical user interfaces of general purpose with a basic collection of visual components, maintaining the coherence with the rest of the programmers’ code. Behind the scenes, the library will use WxErlang for the construction of the interfaces, while the programmer will only have to interact with the functions of ElixirElm. Keywords Elixir, WxWidgets, WxErlang, Elm, GUI, library, graphical user interface, functional language. 6
I Introducción El objetivo de este Trabajo de Fin de Grado es la creación de una librería que facilite el manejo de interfaces gráficas de usuario en el lenguaje Elixir. Esta librería estará basada en la arquitectura utilizada en el lenguaje Elm. A lo largo de este capítulo se desarrollará la explicación de los antecedentes que motivaron este trabajo, así como los objetivos que se fueron planteando para este durante el desarrollo del mismo. Además, se incluye una descripción en detalle del plan de trabajo en cada fase del desarrollo. Antecedentes ¿Qué es Elixir? Para entender las necesidades que motivaron la realización de este proyecto es importante explicar este lenguaje de programación ya que el trabajo que está orientado a sus necesidades. Elixir es un lenguaje de programación funcional orientado a procesos y que está construido sobre otro lenguaje de características similares: el lenguaje Erlang. Entre sus principales características está el soporte para metaprogramación, mediante el cual se permite ampliar la sintaxis del propio lenguaje con el fin de incorporar construcciones específicas de un dominio. Motivación Pese a todo el potencial de Elixir, este lenguaje tiene ciertas carencias a la hora de construir interfaces gráficas de usuario (GUIs). Para llevar a cabo esta tarea, Elixir actualmente utiliza una biblioteca heredada del lenguaje sobre el que está construido, la biblioteca WxErlang. Además, esta biblioteca está adaptada a su vez desde la original WxWidgets de C++. Esto implica que, al estar WxWidgets basado en un paradigma imperativo orientado a objetos, la creación de GUIs en Elixir utilizando WxErlang se realiza de un modo poco natural. Como contraposición, Elm es un lenguaje de programación funcional orientado a la programación de GUIs para navegadores web. Este lenguaje implementa una sencilla forma de programar GUIs, pero carece de otras características clave de Elixir. Entre las principales desventajas de Elm está su ámbito, que se limita principalmente a las aplicaciones web. Así pues, en este trabajo se pretende integrar las facilidades que otorga la arquitectura de Elm a la hora de desarrollar las GUIs al lenguaje de Elixir. 7
Objetivos Como conclusión a los antecedentes, el objetivo principal sobre el que se basa este TFG es el de desarrollar una librería que otorgue una capa de abstracción sobre WxErlang que proporcione al programador un modelo más adecuado para el desarrollo de GUIs en Elixir. A raíz de este objetivo principal se han planteado una pequeña lista con otros objetivos que detallan el desarrollo de la librería. Estos objetivos son: ●Aproximar la sintaxis resultante de la capa de abstracción al lenguaje Elm, ya que esta es bastante simple y fácil de utilizar. En este modelo, el programador deberá de especificar el modelo de la interfaz y programar funciones para la inicializar o modificar un VDOM. ● Otorgar soporte para los elementos básicos de una interfaz gráfica de usuario. Así pues, se implementará soporte para los elementos que se han considerado básicos en cualquier interfaz de usuario. Estos elementos son: cuadros de texto (static texts en WxErlang), cuadros de entrada de texto (text controls), contenedores de elementos (sizers) y listas de elementos (list boxes). ● Añadir la posibilidad de creación y uso de diálogos modales, permitiendo al programador la capacidad de crear y mostrar estos diálogos a partir de una ventana o de otro diálogo con el fin de profundizar en la capacidad de construcción de GUIs de la librería. Plan de trabajo Durante el desarrollo del TFG, el trabajo a realizar se organizó en fases de 15 días a través de reuniones entre el alumno y el profesor director. Cabe mencionar que durante los periodos de exámenes hubo excepciones con la regularidad de las reuniones. En estas reuniones se establecían metas a alcanzar pasados 15 días y se revisaban las establecidas en la anterior reunión. Dependiendo del estado de esas metas, se decidía si se debía profundizar en estas o apuntar a otras. Estas metas se pueden englobar en los siguientes bloques de trabajo: 1. Familiarización con las tecnologías Pese a tener experiencia con otros lenguajes funcionales, era necesario dedicar las primeras tres fases para familiarizarse con todas las tecnologías que entrarían en juego en los próximos bloques de trabajo. Este bloque está descrito en el capítulo III. Durante este bloque, los esfuerzos se centraron principalmente en la entrada en contacto con Elixir y con la construcción de GUIs con WxErlang. Esto se consiguió, primero, a través de la realización de pequeños ejercicios para comprender la sintaxis general del lenguaje, para luego realizar ejemplos sencillos de interfaces de usuario con WxErlang, donde se entró en contacto con los detalles más 8
específicos de la librería, como las constantes y clases con las que se trabajará en los próximos bloques. 2. Codificación de los elementos de interfaz en árboles de diferencia En este bloque se reúnen las fases que se centraron en crear una estructura de datos recursiva que describiese una interfaz de usuario. Esta estructura debía de ser capaz de recoger los atributos que son necesarios para la instanciación de un elemento de la interfaz con WxErlang, y así representar en detalle las características de cualquier elemento visual. A lo largo de este bloque y dependiendo de las metas de cada fase, los campos de esta estructura de datos fueron cambiando y adaptándose a las necesidades de la fase, hasta finalmente asentarse en los árboles de diferencia definitivos con los que cuenta ElixirElm (ver capítulo V, apartado de Los árboles de diferencia). 3. Construcción de una GUI a través de un árbol de diferencia Tras haber codificado con éxito los elementos de la interfaz en árboles de diferencia se requería poder construir y mostrar por pantalla esta interfaz con la información que otorgaba el árbol de diferencia. Este bloque contiene tan solo una fase de quince días, ya que una vez que se contaba con una estructura de datos para la representación de GUIs, tan solo fue necesario programar un módulo que explorase la estructura e instanciase los elementos. Este módulo está desarrollado en más detalle en el capítulo V, en la descripción del Módulo Builder. 4. Desarrollo de un módulo para hallar diferencias entre interfaces Este bloque de trabajo también se apoya en el segundo bloque, resultando en un bloque que se extendió en tan solo una fase de quince días. Con la implementación de este módulo fue necesaria una modificación en la estructura de los árboles de diferencia, para que pudiesen representar de una forma más cómoda la diferencia entre dos interfaces. Es importante mencionar que esta es una primera versión del módulo de comparación, y a medida que avance el desarrollo de la interfaz será actualizada. La descripción en profundidad de este módulo se puede encontrar en el capítulo V, en el apartado referente al Módulo Comparator. 5. Desarrollo de un módulo para aplicar cambios a interfaces Era necesario un módulo que a raíz de las diferencias halladas gracias al del bloque anterior, actualizase la interfaz que se estaba mostrando por pantalla. El desarrollo del módulo en cuestión ocupó dos fases de quince días cada una, ya que la dificultad del módulo y la poca documentación con la que cuenta WxErlang ralentizaron dicho desarrollo. Tras explorar las posibilidades dentro de las actualizaciones que se pudieran llegar a necesitar fue necesario realizar una revisión de las capacidades descriptivas de 9
programas que hacen uso de esta librería. Esta estructura es directamente imperativa, lo cual choca de frente con el paradigma funcional de Erlang, y por tanto de Elixir, creando la motivación para este TFG. Elm [3] Este lenguaje de programación de interfaces gráficas de usuario para navegadores web es, de forma parecida a Erlang y Elixir, un lenguaje funcional, con inmutabilidad en los datos pero, a diferencia de los anteriores, es estáticamente tipado. Este lenguaje forma interfaces gráficas en HTML a través de un DOM virtual. Es importante hacer mención a este lenguaje de programación, porque, pese a no haber programado ningún fragmento de código en él, el uso de los DOM virtuales y la sintaxis de los programas en Elm son los aspectos principales en los que se basa la capa de abstracción que ofrece la librería ElixirElm que se desarrolla en este TFG. WxHelper WxHelper es un módulo de Elixir, obtenido como dependencia a través de un repositorio de GitHub. Este módulo facilita el uso de wxErlang en Elixir definiendo una función por cada constante de wxWidgets, haciendo que su acceso sea mucho más sencillo. Aun así, se requirió hacer una breve modificación para ignorar las constantes referentes a OpenGL, porque daban lugar a fallos de compilación. Git Git es un sistema de control de versiones de código a través de repositorios. Permite principalmente realizar cambios, revertirlos, crear bifurcaciones y juntar dichas bifurcaciones en el repositorio. Para gestionar el repositorio se ha utilizado GitHub, una herramienta web que permite el almacenamiento y seguimiento de los repositorios. Esta herramienta es especialmente útil para que varias personas compartan acceso a los repositorios y ha demostrado ser muy útil para la revisión del código por parte del tutor durante el proceso de desarrollo del TFG. Visual Studio Code Visual Studio Code es el entorno de desarrollo integrado, o en inglés Integrated Development Environment (IDE), que he decidido utilizar para llevar a cabo la programación de la librería. La decisión de escoger este IDE fué relativamente fácil e inmediata, ya que tiene la capacidad de instalar complementos que asisten en el coloreado de la sintaxis del código en Elixir, autocompletado de palabras reservadas o módulos programados en dicho lenguaje, funcionalidades para mantener un control de versiones en repositorios Git y la 16
capacidad de abrir una consola de comandos dentro del IDE, que permite compilar y ejecutar código en Elixir. Google Drive Se trata de un sistema de almacenamiento en la nube que permite compartir archivos con otras personas a través de correo electrónico o del mismo sistema. El uso principal que ha tenido Google Drive en el TFG ha sido el de mantener una carpeta compartida con el tutor, donde se almacenaba documentación relevante, las actas de las reuniones y en las últimas etapas, los distintos capítulos de la memoria. Documentos de Google Documentos de Google es una plataforma web que recoge una colección de aplicaciones web de ofimática que permiten la creación de archivos de texto enriquecido, presentaciones de diapositivas, hojas de cálculo y demás, que pueden ser editadas y accedidas por varios usuarios al mismo tiempo. Se ha utilizado para tanto la redacción de las actas en las reuniones alumno-tutor como para la redacción de los capítulos de la memoria del TFG. La capacidad de hacer ediciones simultáneas que faciliten la corrección de textos y de compartirlo con varias personas han sido las principales características que han llevado a escoger esta plataforma como sistema de edición. Google Meet Esta aplicación de Google implementa un servicio de videotelefonía que permite la creación de salas de conferencia para realizar reuniones de forma telemática entre dos o más personas. Cabe hacer una mención especial a este servicio, ya que es a través de él como se realizaron las últimas reuniones debido a la imposibilidad de hacerlas presenciales. 17
IV Arquitectura básica Llegado el momento de hacer uso de la librería ElixirElm dentro de un programa, el programador deberá implementar ciertas funciones básicas (name(),initial_model(), render() yupdate()). Una vez implementadas, simplemente hará una llamada a la función start_gui() del módulo Elixir_elm para iniciar la GUI, pasando por parámetro el nombre del módulo donde se hayan implementando las funciones anteriores. A continuación se explican las funciones a implementar, todas ellas alrededor de un ejemplo simple de un contador, con dos botones y un cuadro de texto, que es el que se muestra en la siguiente figura: El modelo de funcionamiento de la biblioteca requiere que se mantenga un modelo de la interfaz, que podrá ir variando a lo largo de la ejecución del programa a través de mensajes enviados por los eventos de la interfaz. Estos mensajes deberán ser definidos por el programador al igual que el cambio en el modelo que estos suponen. En esta GUI de ejemplo el modelo será simplemente un número entero para indicar el valor del contador en un momento dado. Los mensajes serán de dos tipos, el mensaje de :decrement asignado al botón de “-” e :increment asignado al botón de “+”. Función name() Esta función sirve para dar título a la ventana que contendrá la interfaz. Tanto los módulos que hagan el papel de ventana principal como los que hagan de diálogos modales deberán implementar este método. Esta función no admite parámetros y deberá devolver un valor del tipo String. A continuación se incluye un pequeño ejemplo de esta función. defmodule Example do ... @spec name :: String.t() def name(), do: "Esto es un título" ... end 18
Función initial_model() El propósito de esta función es el de devolver un map que contendrá el estado inicial del modelo de la interfaz. initial_model no admite ningún parámetro y puede devolver cualquier información que el usuario requiera almacenar en el modelo. defmodule Example do @type model() :: any() ... @spec initial_model :: model() def initial_model() do 0 end ... end En el ejemplo anterior hemos especificado que modelo inicial es el valor 0, indicando el valor inicial del contador. Función update() Esta función define las transformaciones que sufre el modelo cada vez que se produce un evento de la interfaz. En particular, en esta función se recogen los distintos mensajes que debe recibir la vista para modificar el modelo. Estos mensajes pueden ser de cualquier tipo, y la vista no deberá de hacer uso de ningún otro que no haya sido contemplado en esta función. La función recibirá por parámetro el modelo antes de que se produjera el evento y el mensaje asociado a dicho evento. Por último, el valor de retorno será el modelo con las modificaciones necesarias. defmodule Example do @type model() :: any() ... @spec update(model(), any()) :: model() def update(model, :increment), do: model + 1 def update(model, :decrement), do: model - 1 ... end En la función anterior hacemos que cada vez que se reciba un mensaje increment, el cual se produce cuando se pulsa el botón “+” de la interfaz, el modelo (un número entero cualquiera) se incrementará en uno y será retornado por la función. A su vez, cuando se 19
reciba un mensaje decrement al hacer un click sobre el botón “-” se devolverá el modelo se decrementado en uno. Una vez ejecutada esta función, el modelo resultante servirá para crear un árbol de diferencia con ese estado actual y, posteriormente, aplicar los cambios necesarios que se necesiten aplicar en la interfaz (en este ejemplo, cambiar el valor del contador). Funciones constructoras Antes de explicar el funcionamiento de la función render(), es importante hablar de las funciones constructoras de los elementos de la interfaz. Estas funciones del módulo Builder simplifican la creación de elementos de la vista y devuelven un elemento del tipo VDom.t() a partir del cual se construirá la interfaz. Existe una función de este tipo por cada elemento de interfaz implementado en la librería, y estas funciones se organizan en dos tipos según sus características: elementos visuales (botones, textos estáticos, controles de texto y listas) y contenedores. Ambas son similares en los dos primeros argumentos que reciben: una lista con las características del elemento (etiqueta, dimensiones, etc), y el identificador numérico único del elemento en cuestión. Sin embargo, las constructoras de los elementos visuales podrán recibir una lista opcional de pares evento-mensaje, mientras que los contenedores recibirán una lista con las vistas anidadas en ellos. Además de estos, existe una constructora adicional reservada a los diálogos, que recibirá las opciones del cuadro de diálogo, el identificador, las vistas anidadas en el diálogo y el tipo de evento que lanzará al ser cerrado. Función render() La función render() se hará cargo de, dado un modelo, crear una estructura para la vista de la interfaz. La función recibirá como parámetro el modelo (que podrá ser de cualquier tipo) para devolver un VDom.t() con la estructura de la vista. defmodule Example do @type model() :: any() ... @spec render(model()) :: VDom.t() def render(model) do Builder.sizer([orientation: :vertical], 0, [ Builder.button([label: "+"], 1, on_click: :increment), Builder.static_text([label: Integer.to_string(model)], 2), Builder.button([label: "-"], 3, on_click: :decrement) ]) end ... end 20
La función render, y por tanto el árbol de diferencia que genera, son intrínsecamente dependientes del modelo de la interfaz, que se pasa por parámetro a la función. Es necesario que la interfaz que se vaya a construir esté incluida en un sizer principal, que haga las funciones de elemento padre a lo contenido en él. En el caso de nuestro ejemplo, el valor del modelo tan solo modificará el texto que haya en la salida de texto de la interfaz, que será necesario transformar en un valor string y especificando ese valor como el atributo label del static text. Interacciones entre las funciones Una vez el programador haya programado todas las funciones requeridas por ElixirElm, estará preparado para ejecutar su programa. El programador deberá llamar a la función start_gui, y la librería empezará a hacer las llamadas necesarias a las funciones anteriores. El flujo de la librería es el siguiente: primero, se llamará a initial_model(), para tener el valor inicial del modelo, se llamará a la función render() con ese modelo inicial, con el árbol de diferencias devuelto por render() la librería mostrará por pantalla la interfaz y se quedará a la espera de cualquier evento producida por esta. Una vez que se haya recibido un evento, se extraerá el mensaje de este y se llamará a la función update() con el modelo actual y el mensaje recibido para recibir un nuevo modelo, el cual será pasado por parámetro de nuevo a la función render() para obtener el nuevo árbol de diferencias. Con ese nuevo árbol se aplicarán los cambios necesarios a la interfaz y se volverá a quedar a la espera de cualquier evento. A continuación se incluye un gráfico explicativo: 21
Comparación entre Elixir_elm y wxWidgets La estructura final del código (implementando una función start() para lanzar la ejecución del programa) haciendo uso de la librería Elixir_elm es la siguiente: defmodule Example do @type model() :: any() def start() do Elixir_elm.start_gui(__MODULE__) end @spec name :: String.t() def name(), do: "Esto es un título" @spec initial_model :: model() def initial_model() do 0 end @spec render(model()) :: VDom.t() def render(model) do Builder.sizer([orientation: :vertical], 0, [ Builder.button([label: "+"], 1, on_click: :increment), Builder.static_text([label: Integer.to_string(model)], 2), Builder.button([label: "-"], 3, on_click: :decrement) ]) end @spec update(model(), any()) :: model() def update(model, :increment), do: model + 1 def update(model, :decrement), do: model - 1 end Mientras que la estructura de un programa equivalente sin hacer uso de Elixir_elm es: defmodule LongExample do def start() do :wx.new() frame = :wxFrame.new(:wx.null(), WxHelper.wxID_ANY(), 22
"Esto es un título") panel = :wxPanel.new(frame) container = :wxBoxSizer.new(WxHelper.wxVERTICAL()) top = :wxButton.new(panel, 1, label: "+") inside_lbl = :wxStaticText.new(panel, 9, "0") bot = :wxButton.new(panel, 2, label: "-") :wxSizer.add(container, top) :wxSizer.add(container, inside_lbl) :wxSizer.add(container, bot) :wxPanel.setSizer(panel, container) :wxSizer.setSizeHints(container, panel) :wxSizer.setSizeHints(container, frame) :wxFrame.show(frame) :wxSizer.setSizeHints(container, panel) :wxSizer.setSizeHints(container, frame) :wxSizer.layout(container) :wxButton.connect(top, :left_down, id: 1, lastId: 1, callback: fn _, _ -> increment_label(inside_lbl) end ) :wxButton.connect(bot, :left_down, id: 2, lastId: 2, callback: fn _, _ -> decrement_label(inside_lbl) end ) end def increment_label(label) do current = label |> :wxStaticText.getLabel() |> List.to_string() {current, _} = Integer.parse(current) label |> :wxStaticText.setLabel(Integer.to_string(current + 1)) :ok end def decrement_label(label) do current = label |> :wxStaticText.getLabel() |> List.to_string() {current, _} = Integer.parse(current) 23
label |> :wxStaticText.setLabel(Integer.to_string(current - 1)) :ok end end Las dos versiones son claramente diferentes. La diferencia más obvia de todas es la extensión de ambos programas: el programa que hace uso de ElixirElm ocupa un total de veintinueve líneas de código, mientras que el programa que programa diréctamente con WxErlang ocupa cincuenta líneas. Además de la extensión, cabe remarcar que el primer programa es más sencillo de entender que el segundo, donde se requiere un conocimiento de WxErlang si se quiere entender cómo se construye la interfaz. Pero la diferencia más importante se da en la sintaxis y la estructura del código, llegando al punto en el que ambos programas parecen haberse realizado en dos lenguajes de programación totalmente distintos, el primero en un lenguaje funcional y de más alto nivel, mientras que el segundo parece haberse programado en un lenguaje imperativo, y que al no poder mantener un modelo como tal, complica la modificación de la interfaz a través de los eventos. 24
V Arquitectura general de la librería En este capítulo se detalla la estructura de ElixirElm. Esta divide su estructura interna en 4 módulos: Builder,Comparator,GUIUpdater yElixir_elm. También es importante mencionar que ElixirElm implementa una variante de los VDOM: los árboles de diferencia. Tanto los módulos de la librería como estos árboles se explican más en detalle a continuación. Los árboles de diferencia Un árbol de diferencia es una estructura de datos de uso interno de la librería. Estos árboles contienen, por un lado, la información de los elementos de la interfaz gráfica. Por otro lado, almacenan los cambios que se producen en los nodos cada vez que se actualiza la interfaz gráfica. La información contenida dentro de cada nodo es la siguiente: ●El tipo: un átomo que indica que tipo de elemento representa el nodo actual (botón, cuadro de texto, etiqueta, etc.). Este atributo es importante mantenerlo, ya que la instancia del objeto wxErlang que mantenemos dentro de la estructura de datos no indica de qué tipo es. ●El identificador numérico del elemento: cada uno de los elementos debe estar representado por un identificador numérico único para permitir el uso de eventos. Si dos elementos tienen el mismo valor para el resto de atributos menos para este, se considerarán dos elementos totalmente distintos. ●Una lista con los atributos del elemento: esta lista contiene las peculiaridades del elemento (márgenes, orientación, etc). Es importante mantener esta lista para simplificar la comparación entre elementos equivalentes de árboles. ●Su instancia del SizerItem: una vez que se instancia y agrega un elemento a la interfaz, este es referenciado por un objeto que contiene dicho elemento. A través de esta instancia se puede acceder al elemento en cuestión, y se pueden actualizar los parámetros que no son propios del elemento, sino del contexto en el que se encuentra, como los márgenes, el tamaño o la alineación en la ventana. ●La referencia al elemento padre que los contiene: esta es una referencia al elemento superior donde está construido. En el caso de elementos en las ventanas normales, se referencia al panel principal de la ventana que contiene dicho elemento, y en el caso de los elementos de un diálogo, se hará referencia a este. ●La posición ordinal que ocupan en su contenedor, importante para detectar cambios en la ordenación entre él y sus hermanos. 25
Así pués, de estos dos árboles: La construcción del árbol de diferencias entre el original (derecha) y el objetivo (izquierda) se realizará de la siguiente forma: 32
33
Este módulo es el único que no hace uso de la librería de WxErlang, siendo genérico para cualquier librería gráfica a la que se quiera aplicar. Módulo GUIUpdater GUIUpdater se centra en aplicar las modificaciones necesarias a un árbol de diferencias. Esto se logra haciendo uso de la función apply_changes/3, que recibirá el árbol sobre el cual se van a aplicar los cambios, el árbol de diferencias obtenido del módulo Comparator, y el PId del proceso que maneja los eventos de la GUI, para devolver un nuevo árbol de diferencias que representará el VDOM de la GUI después de aplicar los cambios necesarios y con las listas de diferencias de los nodos vacías. Esta función recorre los nodos de ambos árboles, y dentro de cada uno de estos nodos del árbol de diferencias, recorrerá la lista que contiene estas diferencias para aplicarlas una a una. Para aplicar las diferencias que son relativas a elementos internos de los elementos de ventana (cuadros para introducir o mostrar textos, botones o listas), como cambiar etiquetas, estilos, o eventos, es necesario acceder a la instancia del elemento. Esto se hace obteniendo el objeto Window que contiene la instancia que se guarda en el árbol de diferencia y aplicando a este objeto el cambio necesario. 34
En el caso de los elementos sizer el proceso es parecido si se quieren cambiar, reorientar o reordenar los hijos, pero será necesario obtener la referencia al objeto Sizer de la instancia. En cambio, los cambios sobre los atributos relativos al aspecto de un elemento con respecto al resto de elementos de la ventana, como ampliar márgenes, o cambiar alineamientos se realizarán sobre la instancia del objeto SizerItem. Módulo Elixir_elm Elixir_elm es el módulo principal de la librería. Este y el módulo Builder, donde se encuentran las funciones constructoras, son los únicos módulos con los que el programador tendrá que interactuar. En el caso de Elixir_elm será para hacer una llamada a la función start_gui/1 con el nombre de un módulo por parámetro, y este módulo se encargará empezar la ejecución de la GUI. Dentro de Elixir_elm se encuentran, además de la función que inicia la GUI, dos funciones recursivas, que se lanzarán cada una en un proceso distinto. El bucle que se lanza en un segundo proceso sirve para convertir los eventos que lanzan los elementos en WxWidgets a una tupla átomo-mensaje más sencilla de procesar. Esta tupla se enviará al bucle del proceso principal para que haga los cambios necesarios en la GUI. El bucle que se ejecutará en el proceso principal sirve para, una vez recibido un mensaje producido por un evento, que se actualice el modelo, se genere el árbol con la apariencia con el modelo actualizado, se cree un árbol de diferencias entre el árbol antiguo y el nuevo, y por último, a través de este último árbol de diferencias, se actualicen los elementos de la GUI. El código de este bucle es el siguiente: def loop(module, model, dom, receiver) do receive do :exit -> :wx.destroy() false {:gui_event, :exit_modal} -> dom.instance |> :wxDialog.endModal(0) true {:gui_event, message} -> model = module.update(model, message) command = model |> get_command() model = model |> get_model() new_dom = module.render(model) dtree = Comp.get_diff(dom, new_dom) IO.inspect(dtree) dom = GUIUpdater.apply_changes(dom, dtree, receiver) 35
if(dom.type == :sizer) do dom.instance |> :wxSizerItem.getSizer() |> :wxSizer.layout() dom.top_panel |> :wxPanel.layout() end if(execute_command(command, receiver, dom)) do loop(module, model, dom, receiver) else true end end end Hay un elemento particular dentro de este módulo, los comandos. Los comandos son instrucciones que el programador da a la librería con el objetivo de crear o destruir ventanas y diálogos. Para mandar un comando a la librería, el programador deberá de devolver por la función update una tupla con el modelo y el comando a ejecutar. Los comandos actuales de la librería son: ● Show modal: este comando está compuesto por una tupla, en la que el programador incluirá el átomo :show_modal, con el árbol de diferencias de un diálogo que quiera mostrar. Una vez enviado este comando a la librería, un diálogo modal acorde al árbol de diferencias otorgado por el programador es mostrado por pantalla. ● Return modal: está compuesto por una tupla con el átomo :return_modal y el mensaje que se quiere devolver. Sirve para que, a través de un evento provocado por un elemento del diálogo, dicho diálogo se cierre, haciendo una llamada a update con el modelo de la ventana o diálogo que mostró el que se acaba de cerrar y el mensaje que da el comando. ● Close modal: este comando está tan solo compuesto por el átomo :close_modal, y sirve para cerrar un diálogo modal sin devolver nada. ● Exit: este comando, compuesto tan solo por el átomo :exit, termina la ejecución de la interfaz. 36
VI Conclusiones y trabajo futuro En este capítulo final de la memoria se desarrollan las conclusiones y resultados que surgen a raíz de este TFG y del desarrollo del mismo. Además, también se relata una posible dirección a la que pudiera orientarse el trabajo sobre la biblioteca ElixirElm en un futuro. Conclusiones y objetivos Tanto el objetivo principal que se planteó para este TFG como los objetivos derivados de este, han sido cumplidos en su totalidad. A continuación se detalla la revisión de cada uno de esos objetivos: 1. Desarrollar una librería que otorgue una capa de abstracción sobre WxErlang que proporcione al programador un modelo más adecuado para el desarrollo de GUIs en Elixir Este objetivo se cumplió satisfactoriamente, puesto que la abstracción sobre el modelo de desarrollo de GUIs que otorga la librería permite que el programador mantenga la coherencia del paradigma funcional dentro del código de su GUI. El programador no necesita interactuar con la biblioteca WxErlang en ningún momento, ya que se han implementado sustituciones de las constantes que usa ElixirElm para facilitar más aún su uso. 2. Aproximar la sintaxis resultante de la capa de abstracción al lenguaje Elm Consideramos como cumplido este objetivo, ya que la sintaxis de los programas que utilizan ElixirElm requieren, al igual que en Elm, la implementación de funciones que modifiquen un DOM virtual a través de cambios en un modelo, que procesen cambios en un modelo a través de eventos y funciones que den el DOM inicial de una interfaz. 3. Otorgar soporte para los elementos básicos de una interfaz gráfica de usuario. En la versión final de ElixirElm, el programador puede desarrollar cualquier GUI que contenga elementos para entrada/salida de texto, botones, listas de elementos, contenedores y diálogos modales. Consideramos que estos elementos son la base para que se puedan desarrollar GUIs con el suficiente potencial descriptivo, dando por cumplido este objetivo. 4. Añadir la posibilidad de creación y uso de diálogos modales Damos el objetivo por cumplido, ya que se incluye la capacidad de diseñar y mostrar diálogos modales a través de eventos lanzados desde la GUI. Esto 37
también permite la creación de diálogos sobre otros diálogos de manera indefinida. Trabajo futuro ElixirElm es una biblioteca con gran potencial para el futuro, ya que la abstracción que otorga a los programadores de Elixir que la utilicen es atractiva tanto por la gran ayuda que supone a estos, como por la capacidad de mejora de su versión actual. A continuación se incluyen algunos ejemplos de mejora para esta biblioteca: ● Ampliación de elementos visuales soportados Pese a que los elementos que ya contiene son más que suficientes para implementar GUI de múltiples propósitos, sería necesario añadir más elementos para la posible construcción de interfaces más complejas. Este proceso sería relativamente sencillo, ya que, como pude ver a la hora de incluir las listas de elementos en la biblioteca (sección tercera, punto octavo del primer o segundo capítulo en español o inglés respectivamente), la adición de elementos a la biblioteca es relativamente sencillo. Esto es así gracias al ajuste de patrones de Elixir, que permite adaptar las funcionalidades de construcción, comparación y actualización de los nuevos elementos con facilidad. Las posibles dificultades que pudiesen presentarse a la hora de llevar a cabo este proyecto surgirían de cara a la biblioteca de WxErlang, ya que sería necesario comprender en detalle las características y peculiaridades de cada elemento a añadir. ● Adaptación de ElixirElm a otras bibliotecas de GUIs Este posible proyecto es mucho más ambicioso que el anterior, ya que incluiría un gran trabajo de modificación en todos los módulos de la biblioteca. Aun así, se trata de algo bastante plausible, ya que el uso de los árboles de diferencia para representar GUIs es un método tan genérico que no depende de la biblioteca gráfica que se utilice. Para lograr esta meta, se podría, tanto hacer genéricos los módulos que existen hasta el momento, como crear los mismos módulos que ya existen para WxErlang pero haciendo uso de otras librerías. Desde el lado del programador, este seleccionaría la biblioteca que desea utilizar, y ElixirElm la utilizará para crear la GUI que el programador haya diseñado. Este proyecto podría ser una ampliación válida de este TFG, sobre todo si fuese desarrollado por las mismas personas, porque cuentan con el conocimiento del funcionamiento de la biblioteca. Aún así, podrían surgir dificultades al tener que tratar con los distintos funcionamientos internos de las bibliotecas a adaptar, ya que tan solo la documentación y familiarización con estas bibliotecas requeriría mucho trabajo. 38
VII Conclusions and future work In this final chapter of the report, the conclusions derived from the development of the library ElixirElm are detailed. Also, the possible directions in which further work on this project could be taking in the future. Conclusions and objectives The main objective that was proposed for this project, as well as the objectives that derived from it were fully accomplished. Below is detailed the review of each and every one of those objectives: 1. To develop a library for Elixir, that gives a layer of abstraction over WxErlang, providing the programmer a more adequate model for the GUIs development in Elixir This objective is completely accomplished, and that is because the abstraction over the GUIs’ development model that ElixirElm grants allows the programmer to maintain cohesion between the functional paradigm of his GUI and the rest of his/her code. The programmer will not need to interact directly with the WxErlang library at any moment as there are implemented substitution to its constants to further simplify its use. 2. To approximate the resulting abstraction layer syntax to Elm’s This objective is considered as accomplished due to the fact that the syntax of the programs that use ElixirElm require, as Elm does, to implement functions that process changes in a model through events, others that modify a VDOM through those said changes and functions that return the initial state of the GUI initial DOM. 3. To grant support for the basic elements that most GUIs use. With the final version of ElixirElm the programmer is able to develop any GUI that contains for either input or output of texts, buttons, selectable items lists, elements containers and modal dialogs. We consider this elements to be the base for developing graphical user interfaces with the sufficient descriptive potential, assuming this objective as fulfilled. 39
4. To add the possibility of creation and usage of modal dialogs This objective is accomplished as well. This is considered this way because the programmer is able to design and show modal dialogs in his GUI through events. This also allows the creation of nested structures of dialogs indefinitely. Future work ElixirElm is a library with great potential for future development as the abstraction that it gives the Elixir programmers is very attractive to them because both the great help it grants when developing GUI applications and the capabilities of improvement this library in its actual version has. Examples of future improvements on the library are listed below: ● Extending the support to more visual components Despite the fact that the visual components ElixirElm contains are enough for implementing multiple purpose GUIs, it will be necessary to add more of this components in order to allowing the programmers to build more complicated interfaces. This process would be relatively easy due to the fact that, as shown on section three, subsection eight of the introduction (first chapter in Spanish, second in English), the implementation of the lists with selectable items was relatively simple, making me believethat the inclusion of more of those components would be easy as well. This ease comes from the usage of pattern matching in the functions that are necessary to modify, needing only to add clauses for every new component added to the library. There can also be difficulties when delivering this task. Those difficulties could come from the need to fully understand the functioning of every new element added in the WxErlang library, probably making it tedious for every new one of those components. ● Generalization of ElixirElm to other graphical libraries This extension is much more ambitious than the previous one, as with this task will come a great deal of modifications to every module programmed in the library, as well as the creation of new others. However, it is something quite possible, because the use of the difference trees to represent GUIs is a generic method to do so, and does not depend on the library it is using to draw those GUIs. To achieve these goal there is the possibility to either transform the existing modules into a generic version of them, or to create the same modules that already exist for WxErlang for the new libraries. Then, the programmer would have only to choose with which library his GUI should be built and ElixirElm will make use of that library. 40
This project could be a valid extension of the library, especially if it were to be developed by the same director or student, as they would already have the necessary knowledge about the functionality of the library and its modules. On the other hand, difficulties may arise from the need to adapt to the different features of the new libraries to which ElixirElm will be adapted, as familiarizing with this new technologies would require a great deal of time and effort. 41