ZuriHac: esercitarsi nella programmazione funzionale

Nel giugno di quest'anno, nella piccola cittadina svizzera di Rapperswil, si è tenuto un evento chiamato ZuriHac. Questa volta ha riunito più di cinquecento amanti dell'Haskell, dai principianti ai padri fondatori della lingua. Sebbene gli organizzatori chiamino questo evento un hackathon, non si tratta di una conferenza o di un hackathon nel senso classico. Il suo formato è diverso da quello dei programmatori tradizionali. Abbiamo saputo di ZuriHac per fortuna, vi abbiamo preso parte e ora consideriamo nostro dovere raccontare l'insolita scoperta!

ZuriHac: esercitarsi nella programmazione funzionale

Chi siamo

Questo articolo è stato preparato da due studenti del 3° anno del programma “Matematica applicata e informatica” presso la Scuola superiore di economia dell'Università nazionale di ricerca - San Pietroburgo: Vasily Alferov ed Elizaveta Vasilenko. La passione per la programmazione funzionale per entrambi è iniziata con una serie di lezioni di D. N. Moskvin al 2° anno di università. Vasily sta attualmente partecipando al programma Google Summer of Code, all'interno del quale sta implementando grafici algebrici in Haskell sotto la guida del team di progetto Alga. Elizaveta ha applicato le competenze di programmazione funzionale acquisite nel corso dedicato all'implementazione dell'algoritmo anti-unificazione con successiva applicazione nella teoria dei tipi.

Formato dell'evento

Il pubblico target sono i proprietari di progetti open source, i programmatori che desiderano partecipare al loro sviluppo, i ricercatori di programmazione funzionale e le persone semplicemente appassionate di Haskell. Quest'anno, gli sviluppatori di oltre cinquanta progetti Haskell open source provenienti da tutto il mondo si sono riuniti presso la sede - la HSR Hochschule für Technik Rapperswil - per parlare dei loro prodotti e attirare nuove persone interessate al loro sviluppo.

ZuriHac: esercitarsi nella programmazione funzionale

Foto da Twitter ZuriHac

Lo schema è molto semplice: devi scrivere in anticipo alcune proposte sul tuo progetto e inviarle agli organizzatori, che pubblicheranno le informazioni sul tuo progetto sulla pagina dell'evento. Inoltre, il primo giorno, gli autori dei progetti hanno trenta secondi per raccontare molto brevemente dal palco cosa stanno facendo e cosa bisogna fare. Quindi le persone interessate cercano gli autori e chiedono in dettaglio i compiti.

Non abbiamo ancora i nostri progetti aperti, ma vogliamo davvero contribuire a quelli esistenti, quindi ci siamo registrati come partecipanti regolari. Nel corso di tre giorni abbiamo lavorato con due gruppi di sviluppatori. Si scopre che lo studio congiunto del codice e della comunicazione dal vivo rende l'interazione tra autori del progetto e contributori molto produttiva: a ZuriHac siamo stati in grado di comprendere aree che erano nuove per noi e siamo stati in grado di aiutare due team completamente diversi, completando un compito in ciascuno dei progetti.

Oltre alla preziosa pratica, allo ZuriHac sono state tenute anche numerose conferenze e masterclass. Ricordiamo in particolare due conferenze. Nel primo di essi, Andrey Mokhov dell'Università di Newcastle ha parlato dei funtori applicativi selettivi, una classe di tipi che dovrebbero diventare intermedi tra i funtori applicativi e le monadi. In un'altra conferenza, uno dei fondatori di Haskell, Simon Peyton Jones, ha parlato di come funziona l'inferenza del tipo nel compilatore GHC.

ZuriHac: esercitarsi nella programmazione funzionale

Conferenza di Simon Peyton Jones. Foto da Twitter ZuriHac

Le masterclass tenute durante l'hackathon sono state suddivise in tre categorie a seconda del livello di preparazione dei partecipanti. Anche i compiti offerti ai partecipanti che hanno aderito allo sviluppo dei progetti sono stati contrassegnati con un livello di difficoltà. La piccola ma amichevole comunità di programmatori funzionali accoglie volentieri i nuovi arrivati ​​tra le sue fila. Per comprendere le lezioni di Andrei Mokhov e Simon Peyton Jones, invece, è stato molto utile il corso di programmazione funzionale che abbiamo seguito all’università.

La registrazione all'evento è gratuita sia per i partecipanti abituali che per gli autori dei progetti. Abbiamo presentato le domande di partecipazione all'inizio di giugno, dopodiché siamo stati rapidamente trasferiti dalla lista d'attesa a quella dei partecipanti confermati.

E ora parleremo dei progetti allo sviluppo dei quali abbiamo preso parte.

pandoc

pandoc è un convertitore universale di documenti di testo, infatti, da qualsiasi formato a qualsiasi. Ad esempio, da docx a pdf o da Markdown a MediaWiki. Il suo autore, John MacFarlane, è professore di filosofia all'Università della California, Berkeley. In generale, Pandoc è piuttosto famoso e alcuni dei nostri amici sono rimasti sorpresi quando hanno appreso che Pandoc è scritto in Haskell.

ZuriHac: esercitarsi nella programmazione funzionale

Elenco dei formati di documento supportati da Pandoc. C'è anche un intero grafico sul sito, ma questa immagine non rientra nell'articolo.

Naturalmente, Pandoc non fornisce la conversione diretta per ogni coppia di formati. Per supportare una così ampia varietà di trasformazioni, viene utilizzata una soluzione architetturale standard: prima l'intero documento viene tradotto in una speciale rappresentazione intermedia interna, quindi da questa rappresentazione interna viene generato un documento in un formato diverso. Gli sviluppatori chiamano la rappresentazione interna “AST”, che sta per Abstract Syntax Tree, o albero sintattico astratto. Puoi guardare la rappresentazione intermedia in modo molto semplice: tutto quello che devi fare è impostare il formato di output su “nativo”

$ cat example.html
<h1>Hello, World!</h1>

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

I lettori che hanno lavorato almeno un po' con Haskell possono già dedurre da questo piccolo esempio che Pandoc è scritto in Haskell: l'output di questo comando è una rappresentazione in stringa delle strutture interne di Pandoc, creata a somiglianza di come viene fatto solitamente in Haskell, ad esempio nella libreria standard.

Quindi, qui puoi vedere che la rappresentazione interna è una struttura ricorsiva, in ogni nodo interno di cui esiste una lista. Ad esempio, al livello più alto c'è un elenco di un elemento: l'intestazione del primo livello con gli attributi "ciao-mondo",[],[]. Nascosto all'interno di questa intestazione c'è un elenco della stringa "Ciao", seguita da uno spazio e dalla stringa "Mondo!".

Come puoi vedere, la rappresentazione interna non è molto diversa dall'HTML. È un albero in cui ogni nodo interno fornisce alcune informazioni sulla formattazione dei suoi discendenti e le foglie contengono il contenuto effettivo del documento.

Se scendiamo al livello di implementazione, il tipo di dati per l'intero documento è definito in questo modo:

data Pandoc = Pandoc Meta [Block]

Qui Block sono proprio i vertici interni menzionati sopra, e Meta sono le metainformazioni sul documento, come titolo, data di creazione, autori - questo è diverso per diversi formati e Pandoc cerca, se possibile, di preservare tali informazioni durante la traduzione dal formato in formato.

Quasi tutti i costruttori del tipo Block, ad esempio Header o Para (paragrafo), accettano attributi e un elenco di vertici di livello inferiore come argomenti - di regola Inline. Ad esempio, Space o Str sono costruttori del tipo Inline e anche il tag HTML si trasforma nel suo speciale Inline. Non vediamo il motivo di fornire una definizione completa di questi tipi, ma notiamo che può essere trovata qui qui.

È interessante notare che il tipo Pandoc è un monoide. Ciò significa che esiste una sorta di documento vuoto e che i documenti possono essere impilati insieme. Questo è comodo da usare durante la scrittura di Reader: puoi suddividere un documento in parti utilizzando una logica arbitraria, analizzarle separatamente e quindi mettere tutto insieme in un unico documento. In questo caso, le metainformazioni verranno raccolte da tutte le parti del documento contemporaneamente.

Quando si converte, ad esempio, da LaTeX a HTML, prima un modulo speciale chiamato LaTeXReader converte il documento di input in AST, quindi un altro modulo chiamato HTMLWriter converte l'AST in HTML. Grazie a questa architettura, non è necessario scrivere un numero quadratico di conversioni: è sufficiente scrivere Reader e Writer per ogni nuovo formato e tutte le possibili coppie di conversioni verranno supportate automaticamente.

È chiaro che un'architettura di questo tipo presenta anche degli svantaggi, da tempo previsti dagli esperti nel campo dell'architettura software. Il più significativo è il costo per apportare modifiche all'albero della sintassi. Se la modifica è abbastanza grave, dovrai modificare il codice in tutti i lettori e scrittori. Ad esempio, una delle sfide che devono affrontare gli sviluppatori Pandoc è il supporto di formati di tabelle complessi. Ora Pandoc può creare solo tabelle molto semplici, con un'intestazione, colonne e un valore in ogni cella. Ad esempio, l'attributo colspan in HTML verrà semplicemente ignorato. Uno dei motivi di questo comportamento è la mancanza di uno schema unificato per rappresentare le tabelle in tutti o almeno molti formati: di conseguenza, non è chiaro in quale forma le tabelle dovrebbero essere archiviate nella rappresentazione interna. Ma anche dopo aver selezionato una visualizzazione specifica, dovrai modificare assolutamente tutti i lettori e gli scrittori che supportano l'utilizzo delle tabelle.

Il linguaggio Haskell è stato scelto non solo per il grande amore degli autori per la programmazione funzionale. Haskell è noto per le sue estese capacità di elaborazione del testo. Un esempio è la biblioteca parsec è una libreria che utilizza attivamente i concetti della programmazione funzionale - monoidi, monadi, funtori applicativi e alternativi - per scrivere parser arbitrari. Tutta la potenza di Parsec può essere vista in esempio da HaskellWiki, dove viene analizzato un parser completo di un semplice linguaggio di programmazione imperativo. Naturalmente Parsec viene utilizzato attivamente anche in Pandoc.

Descritte brevemente, le monadi vengono utilizzate per l'analisi sequenziale, quando viene prima una cosa e poi un'altra. Ad esempio, in questo esempio:

whileParser :: Parser Stmt
whileParser = whiteSpace >> statement

Per prima cosa devi contare lo spazio e poi l'istruzione, che ha anche il tipo Parser Stmt.

I funtori alternativi vengono utilizzati per eseguire il rollback se l'analisi fallisce. Per esempio,

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

Ciò significa che devi provare a leggere l'affermazione tra parentesi o provare a leggere diverse istruzioni in sequenza.

I funtori applicativi vengono utilizzati principalmente come scorciatoie per le monadi. Ad esempio, lascia che la funzione tok legga alcuni token (questa è una vera funzione di LaTeXReader). Diamo un'occhiata a questa combinazione

const <$> tok <*> tok

Leggerà due token di seguito e restituirà il primo.

Per tutte queste classi, Haskell ha bellissimi operatori simbolici, che fanno sembrare la programmazione di Reader come l'arte ASCII. Ammira questo meraviglioso codice.

I nostri compiti erano legati a LaTeXReader. Il compito di Vasily era quello di supportare i comandi mbox e hbox, utili per scrivere pacchetti in LaTeX. Elizabeth era responsabile del supporto del comando epigraph, che consente di creare epigrafi nei documenti LaTeX.

Hatrace

I sistemi operativi simili a UNIX spesso implementano la chiamata di sistema ptrace. È utile per il debug e la simulazione degli ambienti di programma, consentendo di tracciare le chiamate di sistema effettuate dal programma. Ad esempio, l'utilissima utility strace utilizza ptrace internamente.

Hatrace è una libreria che fornisce un'interfaccia per ptrace in Haskell. Il fatto è che ptrace stesso è molto sofisticato ed è piuttosto difficile usarlo direttamente, soprattutto dai linguaggi funzionali.

Hatrace viene eseguito come strace all'avvio e accetta argomenti simili. Si differenzia da strace in quanto è anche una libreria che fornisce un'interfaccia più semplice rispetto alla semplice ptrace.

Con l'aiuto di Hatrace, abbiamo già rilevato uno spiacevole bug nel compilatore GHC Haskell: se viene interrotto nel momento sbagliato, genera file oggetto errati e non li ricompila al riavvio. Lo scripting tramite chiamate di sistema ha reso possibile riprodurre in modo affidabile l'errore in un'unica esecuzione, mentre le uccisioni casuali hanno riprodotto l'errore in circa due ore.

Abbiamo aggiunto interfacce per le chiamate di sistema alla libreria: Elizaveta ha aggiunto brk e Vasily ha aggiunto mmap. Sulla base dei risultati del nostro lavoro, è possibile utilizzare gli argomenti di queste chiamate di sistema in modo più semplice e accurato quando si utilizza la libreria.

Fonte: habr.com

Aggiungi un commento