ZuriHac: practicando programación funcional

En junio de este año, se celebró por décima vez un evento llamado ZuriHac en la pequeña ciudad suiza de Rapperswil. Esta vez, más de quinientos amantes de Haskell se reunieron desde principiantes hasta los padres fundadores de la lengua. Aunque los organizadores llaman a este evento un hackathon, todavía no es una conferencia o un hackathon en el sentido clásico. Su formato es diferente de la programación tradicional. ¡Aprendimos sobre ZuriHac por una coincidencia afortunada, participamos en él y ahora consideramos nuestro deber contar un hallazgo inusual!




Acerca de nosotros


Este artículo fue preparado por dos estudiantes de tercer año del programa de Matemática Aplicada y Ciencias de la Computación de la Escuela Superior de Economía de San Petersburgo: Vasily Alferov y Elizaveta Vasilenko. La pasión por la programación funcional para los dos comenzó con una serie de conferencias de D.N. Moskvin en el segundo año de la universidad. Actualmente, Vasily participa en el programa Google Summer of Code, en cuyo marco se dedica a la implementación de gráficos algebraicos en el idioma Haskell bajo la guía del equipo del proyecto Alga . Elizabeth aplicó las habilidades adquiridas de programación funcional en el trabajo del curso dedicado a la implementación del algoritmo de antiunificación con el uso posterior en la teoría de tipos.

Formato de evento


El público objetivo son los propietarios de proyectos de código abierto, programadores que desean participar en su desarrollo, investigadores de programación funcional y personas apasionadas por Haskell. Este año, el HSR Hochschule für Technik Rapperswil University reunió a desarrolladores de más de cincuenta proyectos de código abierto de Haskell de todo el mundo para hablar sobre sus productos e interesar a las personas frescas en su desarrollo.



Foto de Twitter ZuriHac

El esquema es muy simple: debe escribir algunas propuestas con anticipación sobre su proyecto y enviarlas a los organizadores, quienes publicarán información sobre su proyecto en la página del evento. Además, el primer día, los autores de los proyectos tienen treinta segundos para contar brevemente desde el escenario lo que están haciendo y lo que hay que hacer. Luego, las personas interesadas buscan autores y preguntan en detalle sobre las tareas.

Todavía no tenemos nuestros propios proyectos abiertos, pero realmente queremos contribuir a los proyectos existentes, por lo que nos registramos como participantes regulares. Durante tres días trabajamos con dos equipos de desarrollo. Resulta que el estudio conjunto del código y la comunicación en vivo hace que la interacción de los autores y colaboradores del proyecto sea muy productiva: en ZuriHac pudimos descubrir nuevas áreas para nosotros y pudimos ayudar a dos equipos completamente diferentes al cerrar la tarea en cada uno de los proyectos.

Además de la práctica valiosa, también se impartieron varias conferencias y clases magistrales en ZuriHac. Recordamos especialmente dos conferencias. En el primero de ellos, Andrei Mokhov, de la Universidad de Newcastle, habló sobre los functores aplicativos selectivos, una clase de tipo que debería convertirse en intermedia entre functores aplicativos y mónadas. En otra conferencia, uno de los fundadores de Haskell, Simon Peyton Jones, habló sobre cómo funciona la inferencia de tipos en el compilador de GHC.



Conferencia de Simon Peyton Jones. Foto de Twitter ZuriHac

Las clases magistrales realizadas durante el hackathon se dividieron en tres categorías según el nivel de capacitación de los participantes. Las tareas ofrecidas a los participantes que se unieron al desarrollo de proyectos también tenían notas con niveles de dificultad. La pequeña pero amigable comunidad de programadores funcionales se complace en dar la bienvenida a los recién llegados a sus filas. Sin embargo, para comprender las conferencias de Andrei Mokhov y Simon Peyton Jones, el curso de programación funcional que se aprobó en la universidad fue muy útil para nosotros.

Tanto para los participantes ordinarios como para los autores del proyecto, la inscripción al evento es gratuita. Solicitamos la participación a principios de junio, después de lo cual fuimos transferidos rápidamente de la lista de espera a la lista de participantes confirmados.

Y ahora hablaremos sobre proyectos en el desarrollo de los cuales participamos.

Pandoc


Pandoc es un convertidor universal de documentos de texto, de hecho, de cualquier formato a cualquiera. Por ejemplo, de docx a pdf, o de Markdown a MediaWiki. Su autor, John MacFarlane, es profesor de filosofía en la Universidad de California, Berkeley. En general, Pandoc es bastante famoso, y algunos de nuestros amigos se sorprendieron cuando supieron que Pandoc estaba escrito en Haskell.



Lista de formatos de documentos compatibles con Pandoc. El sitio también tiene un gráfico completo, pero esta imagen no cabe en el artículo.

Por supuesto, Pandoc no implementa la conversión directa para cada par de formatos. Para soportar un conjunto tan extenso de transformaciones, se utiliza una solución arquitectónica estándar: primero, todo el documento se traduce en una representación intermedia interna especial, y luego se genera un documento en un formato diferente a partir de esta representación interna. Los desarrolladores llaman a la representación interna "AST", que significa Árbol de sintaxis abstracta o árbol de sintaxis abstracta . Puede ver la representación intermedia de manera muy simple: para esto solo necesita establecer "nativo" como formato de salida

$ cat example.html <h1>Hello, World!</h1> $ pandoc -f html -t native example.html [Header 1 ("hello-world",[],[]) [Str "Hello,",Space,Str "World!"]] 

Los lectores que han trabajado con Haskell al menos un poco ya pueden suponer que Pandoc está escrito específicamente en Haskell: la salida de este comando es una representación de estructura interna de Pandoc como una cadena, creada en la forma en que generalmente se hace en Haskell, por ejemplo, en la biblioteca estándar.

Entonces, aquí puede ver que la representación interna es una estructura recursiva, en cada nodo interno del cual hay una lista. Por ejemplo, en el nivel superior hay una lista de un elemento: el encabezado del primer nivel con los atributos "hello-world", [], []. Dentro de este encabezado hay una lista de la cadena "Hola", un espacio y la cadena "¡Mundo!".

Como puede ver, la representación interna no es muy diferente de HTML. Es un árbol, donde cada nodo interno informa cierta información sobre el formato de sus descendientes, y las hojas contienen el contenido real del documento.

Si baja al nivel de una implementación específica, el tipo de datos para todo el documento se define así:

 data Pandoc = Pandoc Meta [Block] 

Aquí, Block es precisamente los picos internos mencionados anteriormente, y Meta es metainformación sobre el documento, como el título, la fecha de creación, los autores; esto es diferente para diferentes formatos, y Pandoc intenta guardar dicha información siempre que sea posible cuando se transfiere de formato a formato.

Casi todos los constructores del tipo Bloque, por ejemplo, Encabezado o Para (párrafo), toman atributos y una lista de vértices de un nivel inferior: en línea, como regla, como argumentos. Por ejemplo, Space o Str son diseñadores del tipo Inline, y la etiqueta HTML también se convierte en su Inline especial. No vemos ninguna razón para dar una definición completa de estos tipos, sin embargo, notamos que se puede ver aquí .

Curiosamente, el tipo Pandoc es un monoide. Esto significa que hay algún tipo de documento vacío, y que los documentos se pueden apilar entre sí. Es conveniente usarlo al escribir lectores: puede dividir un documento en partes con lógica arbitraria, analizar cada uno individualmente y luego juntar todo en un solo documento. En este caso, la metainformación se recopilará de todas las partes del documento a la vez.

Al convertir, digamos, de LaTeX a HTML, primero un módulo especial llamado LaTeXReader convierte el documento de entrada a AST, luego otro módulo llamado HTMLWriter convierte AST a HTML. Gracias a esta arquitectura, no es necesario escribir un número cuadrático de conversiones; es suficiente escribir Reader y Writer para cada nuevo formato, y todos los pares posibles de conversiones serán compatibles automáticamente.

Está claro que esta arquitectura también tiene sus inconvenientes, pronosticados durante mucho tiempo por expertos en el campo de la arquitectura de software. El más significativo es el costo de realizar cambios en el árbol de sintaxis. Si el cambio es lo suficientemente grave, tendrá que cambiar el código en todos los lectores y escritores. Por ejemplo, uno de los desafíos que enfrentan los desarrolladores de Pandoc es admitir formatos de tabla complejos. Ahora Pandoc solo puede en las tablas más simples, con un encabezado, columnas y valor en cada celda. Digamos que el atributo colspan en HTML simplemente será ignorado. Una de las razones de este comportamiento es la falta de un esquema de representación de una sola tabla en todos o al menos en muchos formatos; en consecuencia, no está claro en qué tablas de forma se deben almacenar en la representación interna. Pero incluso después de elegir una vista específica, será necesario cambiar absolutamente todos los lectores y escritores que admitan trabajar con tablas.

Haskell fue elegido no solo por un gran amor por los autores por la programación funcional. Haskell es conocido por sus poderosas capacidades de procesamiento de texto. Un ejemplo es la biblioteca parsec , una biblioteca que utiliza activamente los conceptos de programación funcional (monoides, mónadas, aplicadores y functores alternativos) para escribir analizadores arbitrarios. El poder completo de Parsec se puede ver en el ejemplo de HaskellWiki, que analiza el analizador completo de un lenguaje de programación imperativo simple. Por supuesto, Parsec también se usa activamente en Pandoc.

En resumen, las mónadas se utilizan para el análisis secuencial cuando uno viene primero y luego el otro. Por ejemplo, en este ejemplo:

 whileParser :: Parser Stmt whileParser = whiteSpace >> statement 

Primero debe considerar un espacio y luego una declaración, que también tiene el tipo Parser Stmt.

Los functores alternativos se utilizan para revertir si falla el análisis. Por ejemplo

 statement :: Parser Stmt statement = parens statement <|> sequenceOfStmt 

Significa que debe intentar leer la declaración entre paréntesis o intentar secuencialmente leer varias declaraciones.

Los functores aplicativos se utilizan principalmente como atajos para mónadas. Por ejemplo, deje que la función tok lea algún tipo de token (esta es la función real de LaTeXReader). Veamos tal combinación

 const <$> tok <*> tok 

Ella leerá dos fichas seguidas y devolverá la primera.

Haskell tiene hermosos operadores simbólicos para todas estas clases, lo que hace que los lectores de programación se vean como arte ASCII. Solo admire este maravilloso código.

Nuestras tareas estaban relacionadas con LaTeXReader. La tarea de Vasily era admitir los comandos \ mbox y \ hbox, útiles al escribir paquetes en LaTeX. Elizabeth fue responsable del apoyo del equipo \ epigraph, que permite la ejecución de epigraphs en documentos LaTeX.

Hatrace


En sistemas operativos tipo UNIX, la llamada al sistema ptrace a menudo se implementa. Es útil para depurar y simular entornos de programas, ya que le permite realizar un seguimiento de las llamadas del sistema que realiza el programa. Por ejemplo, la utilidad strace muy útil usa ptrace dentro de sí misma.

Hatrace es una biblioteca que proporciona una interfaz para ptrace en Haskell. El hecho es que ptrace en sí es muy sofisticado y es bastante difícil usarlo directamente, especialmente desde lenguajes funcionales.

Hatrace al inicio se ejecuta como strace y acepta argumentos similares. Su diferencia con strace es que también es una biblioteca que proporciona una interfaz más simple que solo ptrace.

Hatrace ya detectó un error desagradable en el compilador Haskell GHC: cuando se elimina en el momento incorrecto, genera archivos de objetos incorrectos y no los vuelve a compilar cuando se reinicia. Las secuencias de comandos en las llamadas del sistema permitieron reproducir de forma segura el error en una ejecución, cuando las ejecuciones aleatorias reprodujeron el error en aproximadamente dos horas.

Agregamos interfaces de llamadas del sistema a la biblioteca: Elizabeth agregó brk y Vasily agregó mmap. De acuerdo con los resultados de nuestro trabajo, podemos usar los argumentos de estas llamadas al sistema de manera más fácil y precisa al usar la biblioteca.

Source: https://habr.com/ru/post/460537/


All Articles