Quando: Martedì, 16 marzo 2021
Chi: Chiunque sia interessato al rapporto tra data science e programmazione funzionale.
Cosa faremo: Data science: è sulla bocca di tutti, è stato per anni uno dei lavori più ambiti del settore ma gira molta confusione.
In questo talk analizziamo alcuni concetti chiave in Data Science e come certi compiti siano facilmente formulabili in termini funzionali (es. map/reduce, batch filtering, operazioni aggregate etc.). Nel talk terremo un workshop pratico introducendo Apache Spark e Apache Arrow e verrà brevemente presentato il linguaggio Scala, un solido linguaggio multi-paradigma, fortemente funzionale, basato sulla JVM e adatto per la data science.
Speaker: Enrico Trombetta
Enrico Trombetta è uno studente laureando all’università di Glasgow.
Ha lavorato come data scientist intern a Morgan Stanley e come stagista ricercatore nel Glasgow Representation and Information Learning Lab su Knowledge Graph linguistici e Agenti Conversazionali (comunemente detti chatbot).
È membro di Haskell ITA da un anno circa.
Cosa portare: Tanta curiosità e voglia di imparare
Grazie a chi: CodeGarden Roma, The Communities Bay
Per iscriversi: eventbrite
]]>Quando: Sabato 30 marzo 2019 dalle 10:30 alle 13:00
Chi: Chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: Il seminario si propone di: * discutere aspetti teorici e tecnologie emergenti nell’ambito dei Linguaggi di Programmazione * creare un punto d’incontro, confronto e discussione tra ricerca accademica e professionisti.
Ci saranno tre presentazioni da mezz’ora ciascuna e mezz’ora di tavola rotonda alla fine.
Programma:
Cosa portare: Tanta curiosità e voglia di imparare
Grazie a chi: Dipartimento di Informatica
Per iscriversi: metooo.io
]]>La mailing list di TAPAS è qui, il canale Slack per le discussioni relative a TAPAS è all’interno di quello di Code Garden Roma.
Il prof. Salvo ha parlato del suo articolo riguardante la generazione dei numeri primi facendo uso di liste lazy.
In particolare ha discusso una soluzione al “Crivello di Eratostene” che fa uso della generalizzazione ai “Numeri di Hamming” per filtrare i primi.
Il suo lavoro ha mostrato come Haskell e la lazyness si prestano a definire una corrispondenza molto stretta fra descrizione teorica della soluzione e algoritmo effettivo, per ottenere risultati piu’ efficienti degli algoritmi ottimizati tradizionali.
Inoltre ha fatto notare come il compilatore puo’ riservare delle strane sorprese in termini di efficienza…
Il riepilogo degli argomenti trattati è questo:
Nell’intervento dedicato a Rust si è parlato di come viene gestita la memoria, dei puntatori mutabili ed immutabili.
Alla fine si è concordato sul fare un evento TAPAS ogni due mesi.
]]>Quando: Sabato 26 gennaio 2019 dalle 10:30 alle 12:30
Chi: chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: Il seminario si propone di discutere aspetti teorici e tecnologie emergenti nell’ambito dei Linguaggi di Programmazione e creare un punto d’incontro, confronto e discussione tra ricerca accademica e professionisti.
Ci saranno tre presentazioni da mezz’ora ciascuna e mezz’ora di tavola rotonda alla fine.
Programma:
Cosa portare: tanta curiosità e voglia di imparare
Grazie a chi: Dipartimento di Informatica
Per iscriversi: metooo.io
]]>Quando: Domenica 1 Luglio, dalle 10:00 alle 16:00.
Chi: chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: L’ultimo weekend di Giugno ci incontriamo a Mikamai per condividere le nostre esperienze nello sviluppo con Haskell. Ci saranno anche due ospiti che lavorano in Purescript (e Haskell) molto preparati: Fabrizio Ferrai e Justin Woo. Justin terrà anche una presentazione in inglese:
Superior string spaghetti with purescript-jajanmen
In this talk, Justin will talk about how PureScript can now parse type level strings by deconstructing them with Symbol Cons and match on overlapping instances using instance chains. With these techniques, he’ll demonstrate how he makes better string spaghetti with his library Jajanmen to synthesize row types from symbols.
Questa è invece la presentazione di Fabrizio:
Purely Functional DevOps with Dhall
Functional programming is now mainstream in Software Development. Does the same trend apply to Operations as well? Kind of: the DevOps culture is bringing to the table declarative configurations with Infrastructure-as-Code deployments, but this usually happens by employing either full-fledged programming languages or simple data languages (JSON is nicer to type down than XML, but still)…can we do better? In this talk we’ll see how to avoid Turing-completeness and leverage Dhall - a strongly typed, total functional language - to get safety, modularity and reusability while still being able to do dangerous things like template configurations and spin up cloud machines.
Cosa portare: un computer portatile e un ambiente Haskell configurato.
Grazie a chi: MIKAMAI e LinkMe che hanno messo a disposizione gratuitamente la sala.
Per iscriversi: metooo.io
]]>Questo meetup è stato il secondo a tenersi nel centro Italia, dopo Firenze, e ha visto la particepazione di nuovi programmatori decisi ad avvicinarsi ad Haskell.
Il gruppo Beginners
è stato seguito da Luca, sui passi della presentazione Introduzione ad Haskell (se non c’eravate ma volete una prima infarinatura, potete sempre vedere il video registrato all’edizione bolognese dell’Haskell Day).
Dopo la presentazione di Luca, nel gruppo beginners, sotto la guida di Vitalij, Ruggero, Renzo, Francesco e Stefano, sono stati svolti degli esercizi dedicati a list comprehension e alle funzioni di ordine superiore filter, map e foldl: tracce degli esercizi e testi unitari
Gli altri partecipanti si sono concentrati su vari progetti, tra cui un interprete scheme sviluppato in Haskell, perfezionamento di un frontend scritto in Elm (un linguaggio cugino di Haskell) e molte riflessioni sullo stato della programmazione funzionale sia pura che per linguaggi orientati su altri paradigmi.
Siamo molto soddisfatti dalla nostra prima esperienza romana, sia per aver conosciuto nuovi programmatori funzionali, sia per aver rivisto i partecipanti alle scorse edizioni!
Ringraziamo di cuore tutti i partecipanti e contiamo di ritrovarvi tutti presto al prossimo evento!
]]>Quando: Sabato 18 Novembre, dalle 09:00 alle 18:00.
Chi: chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: lavoreremo in gruppi (2-6 persone circa) su diversi progetti. Ogni persona può scegliere il progetto che vuole, saranno date delle indicazioni sul livelo di conoscenza richiesto per contribuire. Lo scopo è provare a utilizzare Haskell per un progetto concreto, “toccare con mano” il modo di lavorare altrui e di condividere le esperienze nelle numerose pause di gruppo. Lo scopo è puramente didattico, quindi nessuna ansia.
Cosa portare: un computer portatile e un ambiente Haskell configurato.
Grazie a chi:
Per iscriversi: eventbrite.it
]]>cabal new-build
e quì c’è una interessante retrospettiva.
Dopo averla letta ho realizzato di quante cose diamo per scontate, ma nascondono dietro una mole impressionante di lavoro.
]]>Inizia la sfida: vediamo se un programmatore Object-Oriented come me, senza una laurea in matematica e categoria delle teorie, e che parte prevenuto, lo riesce a capire! :-)
Intanto esteticamente il codice si presenta molto bene.
Il 99% delle funzioni sono nella Aura
monad, che è così definita:
{- The Aura Monad. Functions of note:
pure : yields a successful value.
failure : yields an error and bypasses all other operations.
catch : catches an error.
wrap : If given an Either, rewraps it into an Aura Monad.
(>>=) : fails on the first error.
liftIO : Perform intermittent IO using `liftIO`.
ask : Obtain run-time settings.
runAura : Unwraps an Aura action. Must be passed `Settings` as well.
-}
newtype Aura a = A { runA :: ExceptT String (ReaderT Settings IO) a }
deriving ( Monad, MonadError String, MonadReader Settings, MonadIO,
Functor, Applicative)
Da notare i commenti sintetici e dritti al punto (ode al programmatore), e come non ci sia boiler-plate code (ode a {-# LANGUAGE GeneralizedNewtypeDeriving #-}
).
Quasi tutte le funzioni o usano la do
notation, oppure usano uno stile applicative, idiomatico e prevedibile, come:
getPacmanHelpMsg = lines <$> pacmanOutput ["-h"]
È vero che l’abito non fa il monaco, ma gli import sono molto ordinati:
import System.Posix.Files (fileExist)
import System.FilePath ((</>))
import Text.Regex.PCRE ((=~))
import Control.Monad (unless)
import Data.List ((\\), sort, groupBy)
import Data.Foldable (traverse_, fold)
import Data.Char (isDigit)
import Data.Monoid ((<>))
Quindi di primo impatto, il codice sembra vincere la sfida: ordinato e con una struttura prevedibile.
Ora la seconda parte della sfida: proverò a studiare due funzioni collegate fra loro, per verificare se la capisco anche nel dettaglio. Sarò iper-critico nell’analisi, ma è ovvio che se avessi scritto io il codice, o non sarebbe mai nato, o sarebbe stato molto peggiore.
Questa la forma originale. Senza una IDE che permetta di navigare nel codice e mostri i tipi, è ovviamente impossibile capire al 100%, ma comunque un’idea se la si riesce a fare:
-- | Interactive. Gives the user a choice as to exactly what versions
-- they want to downgrade to.
downgradePackages :: [String] -> [String] -> Aura ()
downgradePackages _ [] = pure ()
downgradePackages pacOpts pkgs = asks cachePathOf >>= \cachePath -> do
reals <- pkgsInCache pkgs
reportBadDowngradePkgs (pkgs \\ reals)
unless (null reals) $ do
cache <- cacheContents cachePath
choices <- traverse (getDowngradeChoice cache) reals
pacman $ "-U" : pacOpts <> ((cachePath </>) <$> choices)
getDowngradeChoice :: Cache -> String -> Aura String
getDowngradeChoice cache pkg = do
let choices = getChoicesFromCache cache pkg
notify $ getDowngradeChoice_1 pkg
liftIO $ getSelection choices
Iniziamo la disamina:
-- | Interactive. Gives the user a choice as to exactly what versions
-- they want to downgrade to.
downgradePackages :: [String] -> [String] -> Aura ()
downgradePackages _ [] = pure ()
downgradePackages pacOpts pkgs = asks cachePathOf >>= \cachePath -> do
Il commento della funzione è ottimo.
Il codice idem, dato che estrae l’opzione di configurazione cachePathOf
, leggendola dai parametri di configurazione, recuperati dall’environmet della MonadReader
, e esegue una lista di comandi nella Aura
monad. Tra l’altro tutte le funzioni hanno questa struttura in comune.
Il codice segue le best-practices, dato che estende Aura
con tutte le features utili, riducendo gli argomenti delle funzioni, e rendendole componibili fra di loro di default.
Continuiamo…
reals <- pkgsInCache pkgs
reportBadDowngradePkgs (pkgs \\ reals)
unless (null reals) $ do
finezza: unless + negation nei test si potrebbe fraintentedere, più chiaro IMHO qualcosa tipo when (not $ null reals) $ do ...
.
Il codice continua con
cache <- cacheContents cachePath
choices <- traverse (getDowngradeChoice cache) reals
getDowngradeChoice
è una action ma essendo in Aura String
, può essere passata a traverse
in stile simil “funzionale”, quindi molto leggibile. Quello che fa è chiedere all’utente (quindi nella IO monad) se va fatto un downgrade di un package. Quindi trasforma la lista reals
in una lista di scelte dell’utente, passando dalla IO monad.
Questo è il codice della funzione
getDowngradeChoice :: Cache -> String -> Aura String
getDowngradeChoice cache pkg = do
let choices = getChoicesFromCache cache pkg
notify $ getDowngradeChoice_1 pkg
liftIO $ getSelection choices
La funzione è chiara, e navigando nelle funzioni chiamate lo diventa ancora di più. Quindi ottimo.
Torniamo alla funzione originale. La linea dopo:
pacman $ "-U" : pacOpts <> ((cachePath </>) <$> choices)
esegue il comando pacman
passandogli una riga di comando, sotto forma di [ShellArg]
.
Il codice è però criptico dato che usa <>
e <$>
che sono si operatori standard, ma troppo generici e vanno istanziati al nostro caso/type per capire cosa facciano realmente. Nel nostro caso lavorano su una lista di [ShellArg]
, quindi <>
è la concatenazione di liste ++
e <$>
è la fmap
, ovvero la map
di liste.
Quindi il codice equivale a (map (\f -> cachePath </> f) choices)
che semplicemente trasforma una lista di FilePath
relative, in FilePath
assolute. Infatti </>
è una funzione che costruisce una path usando il directory separator di Unix (/
) o di Windows (\
).
"-U" : pacOpts <> ((cachePath </>) <$> choices)
è un esempio di codice che soffre di Haskellism: codice elegante che appaga l’orgoglio intelettuale dell’autore, ma nello stesso tempo frustra il lettore, perchè troppo criptico da interpretare. Frustrazione che rischia di trasformarsi in rabbia, quando il lettore realizza al termine di una (troppo) lunga sessione di studio, che il codice in realtà non sta facendo tutto questo gran che.
Un modo per rendere il codice più chiaro, ma ugualmente elegante, è riscriverlo, rendendo espliciti i tipi coinvolti, e quindi la semantica precisa degli operatori generici coinvolti:
let params::[ShellArg]
= ["-U"]
<> pacOpts
<> ((\filePath -> cachePath </> filePath) <$> choices)
pacman params
In questa forma si capisce subito che stiamo lavorando con delle liste, quindi <>
si associa immediatamente alla concatenazione di liste, abbiamo tolto il :
che confondeva solo, e <$>
si riconduce a fmap
che per le liste è map
.
L’operatore </>
usato in forma infix è più chiaro, anche se meno elegante.
Inoltre l’uso di ben cinque operatori infix diversi ($
, :
, <>
, </>
, <$>
) rende Haskell un linguaggio orientato più agli ideogrammi che alle funzioni, quindi ne ho scelti pochi ma buoni.
Oppure possiamo riscriverlo usando funzioni esplicite, invece della loro variante generica:
pacman $ ["-U"] ++ pacOpts ++ (map (\filePath -> cachePath </> filePath) choices)
In ambedue i casi, il criterio è quello di ridurre il tempo necessario a capire il codice, rispetto a massimizzare l’eleganza fine a se stessa.
La programmazione generica (Monoid
, Functor
, Applicative
) è utile per aumentare il riuso delle funzioni/concetti, ma non è utile quando è più quello che offusca che quello che esprime. In questi casi o si rende più esplicito il codice, oppure le IDE del futuro devono aiutare nella comprensione, dato che diventa più uno studio che una lettura.
Riassunto: il codice di Aura è molto chiaro e relativamente facile da capire, e questo si estende anche ad Haskell, ma non è esente da un pó di Haskellism. Parte della cripticità di molto codice Haskell potrebbe essere ridotta, mettendosi maggiormente nei panni di chi lo deve leggere e capire, sopratutto quando tutto sommato il codice deve fare cose molto semplici. Molta complessità di codice Haskell è semplicemente “gratuita” e andrebbe evitata.
]]>Il gruppo di chi ha deciso di avvicinarsi al linguaggio è stato seguito da Luca, procedendo lungo la traccia della presentazione “Introduzione ad Haskell”. Durante la giornata le nuove leve hanno svolto esercitazioni sulla definizione di nuovi tipi, implementato Type Class e intuito così la definizione di Monade.
Titto ha illustrato la problematica dell’ottimizzazione e dell’efficienza in Haskell e gli strumenti per risolvere questo problema. Partendo da un esempio concreto (una serializzazione di dati sfruttando i generics), il gruppo ha visto come dare indicazioni efficaci al compilatore e come varia l’efficienza all’aumentare della complessità dei tipi. Gran parte del tempo è stata impiegata commentando e analizzando Core, il linguaggio generato dal compilatore come passo intermedio fra il programma Haskell e il codice binario.
Carlo ha presentato la libreria FRP Reflex, illustrandone i tipi di base, il paradigma di funzionamento e le potenzialità. Il gruppo ha installato reflex-dom attraverso nix e svolto un piccolo esempio per vedere Reflex in azione.
Un gruppo di livello intermedio si è focalizzato sulla realizzazione di un webservice usando il framework Servant. La API Rest proposta era molto semplice, affinché fosse possibile realizzarne una implementazione completa nel limitato tempo a disposizione, potendosi quindi rendere conto della maggior parte degli aspetti tecnici legati a un lavoro di questo tipo: definizione delle rotte e dei controller ad esse legati, mantenimento dello stato del server (per semplicità si è optato per una MVar anziché usare un database esterno), codifica dell’output del servizio. A metà pomeriggio si aveva un prototipo funzionante, e i più avventurosi si sono addirittura cimentati nella scrittura di un frontend con Elm. Il codice prodotto è disponibile su github.
]]>Quando: Sabato 25 Febbraio, dalle 09:00 alle 18:00.
Chi: chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: lavoreremo in gruppi (2-6 persone circa) su diversi progetti. Ogni persona può scegliere il progetto che vuole, saranno date delle indicazioni sul livelo di conoscenza richiesto per contribuire. Lo scopo è provare a utilizzare Haskell per un progetto concreto, “toccare con mano” il modo di lavorare altrui e di condividere le esperienze nelle numerose pause di gruppo. Lo scopo è puramente didattico, quindi nessuna ansia.
Cosa portare: un computer portatile e un ambiente Haskell configurato.
Grazie a chi: MIKAMAI e LinkMe che hanno messo a disposizione gratuitamente la sala.
Per iscriversi: metooo.io
]]>Abbiamo aperto un doodle per decidere insieme la data del nostro incontro invernale 2016/2017:
Dove: Milano, Via Giulio Venini, 42, 20127 Milano (MI), IT
Verremo ospitati da Mikamai (dove si è svolto il primo incontro di Haskell ITA!)
Fateci sapere le vostre preferenze e a presto!
]]>Seguendo un approccio ormai collaudato, abbiamo formato piccoli gruppi di lavoro, in modo che a ognuno fosse possibile partecipare, in maniera attiva e alla pari, ad uno o più progetti di proprio gradimento - il tutto in maniera estremamente libera e amichevole.
Un gruppo di volenterosi newbie si è cimentato su esercizi, presi dall’ottimo Haskell Programming from first principles. Come spesso accade, gli esercizi si sono rivelati tutt’altro che semplici - e un confronto con persone più esperte è stato decisamente utile. Ecco un resoconto di quanto imparato.
Paol(in)o ha spiegato l’utilizzo dei FingerTree: una struttura dati utilizzata per modellare sequenze - che sono anche alla base del modulo Data.Sequence
. Il formato della presentazione è stato molto gradito: circa cinque ascoltatori si sono riuniti intorno ad una lavagna, per delineare differenti ipotesi risolutive. L’interattività della spiegazione ha permesso a tutti di capire meglio l’argomento trattato.
Si è organizzato un piccolo simposio per discutere della situazione del web-development in Haskell. Abbiamo considerato principalmente le differenze tra l’approccio FRP (reflex) e quello basato su react (react-flux), volgendo particolare attenzione alle differenze di struttura generale e alla componibilità delle strutture create.
Abbiamo esplorato i problemi di concorrenza che si possono presentare in un’applicazione di rete, prendendo a pretesto un prototipo di gioco multiplayer online che permettesse ai client di accedere ad una scacchiera condivisa applicando una semplice regola: due utenti non possono stare nella stessa cella. Per fare ciò, abbiamo introdotto un’architettura client/server mediante websocket e uno stato condiviso sul server utilizzando MVar. Dopodiché, abbiamo simulato una race condition - introducendo un tempo di attesa tra il primo controllo di disponibilità della cella e l’effettiva modifica: a questo punto, due client che entravano nella stessa cella contemporaneamente potevano farlo! Sostituendo MVar con TVar - tratto da STM - abbiamo ottenuto il controllo della transazione e la possibilità di individuare modifiche esterne allo stato - che abbiamo deciso di risolvere mantenendo il secondo client fermo nella posizione precedente.
Un gruppo di persone ha svolto esercizi utilizzando la libreria Quid2, sotto la guida del suo autore: Titto.
Ponendosi come obiettivo la realizzazione di un semplice client per le web API di Random.org, un gruppetto di nuovi arrivati ha avuto modo di sperimentare l’I/O in Haskell e - coadiuvati da sviluppatori più esperti - di svolgere un primo approfondimento sulla teoria delle monadi.
Il pranzo insieme, i momenti di retrospective durante il meetup, gli scambi di consulenza fra i diversi gruppi e una gita serale/notturna nel centro di Bologna con degustazione di tigelle hanno permesso a tutti di conoscersi.
Ringraziamo gli autori di Haskell Programming from first principles per averci messo a disposizione alcune copie del libro, regalate poi a cinque fortunati Haskellers, e l’associazione Kilowatt, per averci ospitato.
]]>Quando: Sabato 19 Novembre, dalle 10:00 alle 18:00.
Chi: chiunque sia interessato alla programmazione funzionale, indipendentemente dal suo livello di esperienza.
Cosa faremo: lavoreremo in gruppi (2-6 persone circa) su diversi progetti. Ogni persona può scegliere il progetto che vuole, saranno date delle indicazioni sul livelo di conoscenza richiesto per contribuire. Lo scopo è provare a utilizzare Haskell per un progetto concreto, “toccare con mano” il modo che hanno altri di lavorare e di condividere le esperienze nelle numerose pause di gruppo. Lo scopo è puramente didattico, quindi nessuna ansia.
Cosa portare: un computer portatile e un ambiente Haskell configurato.
Come arrivare: dalla Stazione Centrale dei Treni di Bologna, prendere l’autobus 33 (frequenza 15 minuti) e scendere alla fermata “Giardini Margherita” e/o “Porta Santo Stefano” (circa 20 minuti di viaggio). Entrare nei Giardini Margherita, dall’ingresso di Via Castiglione 136 o Via Polischi 9, e recarsi presso “Le Serre dei Giardini”, alla struttura “La Gabbia del Leone”.
Grazie a chi: all’associazione Kilowatt che ha messo a disposizione gratuitamente la sala.
Per iscriversi: https://www.metooo.io/e/haskell-day-bologna
]]>Abbiamo aperto due doodle per decidere insieme il luogo e la data del nostro incontro autunnale:
Fateci sapere le vostre preferenze e a presto!
]]>Partecipanti, fila in piedi (a partire da sinistra): Luca Benci, Alberto, Daniele D’Orazio, Carlo Nucera, Nicola Bonelli, Francesco Ariis, Matteo Baglini, Salvatore Veneziano, Marco Santamaria, Emiliano Anichini.
In ginocchio: Pasqualino Assini (Titto), Massimo Zaniboni, Ruggero D’Alò, Vitalij Zadneprovskij, Nadir Sampaoli, Aurelien Rainone, Francesco Gazzetta.
Dietro, a fare la foto: Luca Molteni :-)
Ritrovo alle 10:00, con la sede di Develer pronta a ospitare, nonostante sia il Sabato prima di Pasqua, il nostro terzo meetup: diciotto programmatori appassionati di Haskell, provenienti da tutta Italia, e pronti a condividere una giornata di programmazione funzionale e divertimento.
Dopo un breve sondaggio sulla preparazione tecnica dei presenti, sono stati proposti i seguenti progetti, su cui lavorare a gruppi:
Questo è stato certamente il progetto più popolare, con nove persone che ci hanno lavorato sopra.
Sono stati scritti quattro bot che si sono interfacciati con l’infrastruttura di Titto, per scambiarsi messaggi in chat.
Grazie all’ottimo tutoraggio di Titto, anche le persone che non avevano un’ampia conoscenza di Haskell sono riusciti a creare qualcosa.
L’articolo è molto interessante, dato che descrive nei dettagli come Opaleye SOT estenda il type system di Haskell, usando equazioni sui tipi, al fine di verificare a compile-time, che le query, scritte usando le Arrow di Haskell, rispettino la struttura logica dello schema del database sottostante.
È stata un’ottima occasione per imparare diverse estensioni di GHC, e capire quale possa essere il loro utilizzo pratico.
In quattro proviamo a risolvere questo semplice issue 1369 su GitHub, relativo al progetto OSS Stack.
Riusciamo a creare una implementazione parziale per le 13:00. Dopo pranzo creiamo la pull request e ci rendiamo conto che abbiamo rotto il comportamento precedente, quindi lavoriamo tutto il pomeriggio nel cercare di esprimere in maniera corretta l’opzione da console con un valore di default, ma non ci riusciamo causa crisi da carboidrato.
Cosa abbiamo imparato:
Carlo ha mostrato e spiegato un’anteprima dei suoi articoli sulle Lens che saranno pubblicati sul blog di Haskell-ITA.
Intanto un doveroso plauso ai programmatori di Develer che oltre a fare gli onori di casa, si sono cimentati con il linguaggio Haskell, mostrando una curiosità e una passione veramente a tutto campo per quel che riguarda il loro lavoro.
In questo terzo meetup abbiamo deciso di privilegiare il lavoro in gruppo e la programmazione, rispetto ai talk. La maggior parte delle persone è riuscita a partecipare al lavoro di due gruppi durante il corso della giornata: uno principale e poi nella parte finale del pomeriggio un progetto minore come un corso sulle Lens, o degli esercizi Kata. Nelle pause fra una sessione di lavoro e un’altra c’è stato modo di chiaccherare e confrontarsi anche fra persone di gruppi differenti.
L’impressione è che ci sia divertiti e non poco, e che si sia creato un buon spirito di gruppo/community, complice anche l’accogliente sala con ampie vetrate e vista sulle montagne.
]]>Sabato 26 Marzo, dalle 10:00 alle 18:00, ospitati da Develer
Develer Srl
via Mugellese 1/A
50013 Campi Bisenzio - Firenze - Italia
Per chi viene in treno:
La libreria definisce una View come
> import Data.Monoid (Monoid(mempty, mappend, mconcat), (<>), First)
> import qualified Data.Monoid as M
>
> newtype View a = AsFold (FoldM IO a ())
FoldM
è un data-type definito in Control.Foldl
.
Andando a recuperare la definizione di FoldM
:
> -- | Like 'Fold', but monadic
> data FoldM m a b = forall x . FoldM (x -> a -> m x) (m x) (x -> m b)
Quindi siccome è “like Fold”, questa è la Fold
:
> {-| Efficient representation of a left fold that preserves the fold's step
> function, initial accumulator, and extraction function
>
> This allows the 'Applicative' instance to assemble derived folds that
> traverse the container only once
> -}
> data Fold a b = forall x . Fold (x -> a -> x) x (x -> b)
Quindi la prima funzione x -> a -> x
è da considerarsi come una funzione di fold, che accetta uno stato di elaborazione intermedio x
, un elemento della “lista” a
e torna il nuovo stato intermedio. Segue lo stato iniziale di elaborazione, come nella fold
. La seconda funzione è invece una funzione finale che viene usata per convertire lo stato finale della fold
nel risultato finale voluto.
E infatti abbiamo questa funzione che esegue una Fold
, come se fosse una fold
:
> -- | Apply a strict left 'Fold' to a 'Foldable' container
> fold :: Foldable f => Fold a b -> f a -> b
> fold (Fold step begin done) as = F.foldr cons done as begin
> where
> cons a k x = k $! step x a
Quindi con Data.Foldl.fold
possiamo passare ad una Fold
un container foldable f a
e avere il risultato finale del fold di tipo b
. Foldable
rappresenta un container che è foldable, quindi di fatto tutte le strutture dati più comuni: liste, vettori, trees, ecc…
Lo scopo di Fold
è quello di scrivere codice che combina due o più funzioni di fold, come
> average :: Num b => Fold b b
> average = (/) <$> sum <*> genericLength
>
> sum :: Num a => Fold a a
> sum = Fold (+) 0 id
>
> genericLength :: Num b => Fold a b
> genericLength = Fold (\n _ -> n + 1) 0 id
La combinazione delle due Fold
sum
e genericLength
, in una implementazione naive richiederebbe una scansione doppia della lista. Qualcosa tipo:
> slowAverage :: Num b => [b] -> b
> slowAverage fs
> = let s = sum fs
> l = length fs
> in s / l
Le regole di combinazione delle operazioni della Fold
permettono invece di scansionare la lista una sola volta, creando un codice equivalente a questo:
> fastAverage :: Num b => [b] -> b
> fastAverage fs
> = let (s,l ) = foldl (\(s1, l1) x -> (s1 + x, l1 + 1)) (0, 0) fs
> in s / l
Questo grazie all’implementazione di Applicative
da parte della Fold
:
> data Pair a b = Pair !a !b
>
> instance Applicative (Fold a) where
> pure b = Fold (\() _ -> ()) () (\() -> b)
>
> (Fold stepL beginL doneL) <*> (Fold stepR beginR doneR) =
> let step (Pair xL xR) a = Pair (stepL xL a) (stepR xR a)
> begin = Pair beginL beginR
> done (Pair xL xR) = (doneL xL) (doneR xR)
> in Fold step begin done
e di
> instance Functor (Fold a) where
> fmap f (Fold step begin done) = Fold step begin (f . done)
Alla luce di questo riguardiamo il codice di esempio
average :: Num b => Fold b b
average = (/) <$> sum <*> genericLength
L’operatore <$>
è un sinonimo della fmap
dei Functor
. La Fold
è un Functor
, quindi è sia in grado di memorizzare un valore di tipo x
, che di applicare al valore una generica funzione x -> y
. La definizione di Functor
è:
class Functor f where
fmap, (<$>) :: (a -> b) -> f a -> f b
La particolarità matematica di un Functor
è quella di non riuscire a variare la struttura del Functor
f a
quando calcola f b
dato che la funzione a -> b
passata come parametro, non ha informazioni sul Functor
sorgente, e non ritorna informazioni sul Functor
destinazione.
Un esempio di fmap
è la map
su una lista. La map
applica una funzione a tutti gli elementi di una lista. Ma non c’è modo in cui la map
possa cambiare il numero di elementi della lista, dato che la funzione f
parametro della map
non ha alcuna informazione riguardo la lista, ma processa solo un elemento alla volta della lista. Inoltre se map
fosse implementata in modo da variare il numero di elementi, non rispetterebbe le propietà dell’elemento neutro e la propietà associativa.
Da notare che Applicative
e poi Monad
hanno una struttura simile, ma permettono alla funzione usata come primo parametro di modificare progressivamente più cose nel risultato finale. Ma lo vedremo in seguito.
Torniamo al nostro esempio. La prima parte dice
average = (/) <$> sum ...
e si noti come nella definizione di Fold
instance Functor (Fold a) where
fmap f (Fold step begin done) = Fold step begin (f . done)
si dica semplicemente che la funzione (/)
debba essere usata come parametro finale della Fold
che se ricordiamo bene è la funzione usata per trasformare lo stato finale della Fold
nel risultato finale tornato. E la cosa torna: la divisione per calcolare la media, non va applicata per ogni elemento, ma solo al termine della scansione della lista.
Da notare che la definizione di fmap
(che sarebbe poi il nome di <$>
) non torni un valore in questo caso, ma torni una Fold
(simil funzione) risulato della combinazione di operatori. Solo quando si applicherà la Fold
ad un container foldable, con un initial state, si avrà il risultato voluto. Ma noi stiamo combinando dei valori Fold
per ora, e non dei valori finali.
Se cerchiamo di fare type checking di (/) <$> sum ...
con (<$>) :: (a -> b) -> f a -> f b
, notiamo che effettivamete l’elemento a sinistra dell’operatore è una funzione e l’elemento a destra la nostra struttura Fold
di tipo Functor
.
Vediamo la seconda parte
( (/) <$> sum) <*> genericLength
In questo caso <*>
è l’operatore usato per combinare fra di loro due valori (funzioni nel nostro caso) di tipo Applicative
. Questa la definizione di Applicative
:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
La definizione di <*>
è molto simile a quella di <$>
solo che invece di avere a -> b
, abbiamo f (a -> b)
come primo argomento. Cosa comporta questo in pratica? Comporta che quando si torna il risultato f b
, il risultato oltre a dipendere da f a
, può anche dipendere da f (a -> b)
e quindi anche dalla struttura del functor f
usato come primo argomento in modo esplicito. Quindi se si usa come Functor
un vettore, e si implementa per esempio la moltiplicazione di matrici, allora il numero di colonne e linee del vettore risultato possono dipendere dal numero di linee e colonne dei due vettori sorgenti. Nel caso di un Functor
e della funzione fmap
non era mai possibile cambiare la struttura del Functor
.
Sia la Applicative
che la Functor
combinano Functor
fra di loro e tornano un altro Functor
. Ma la Applicative
può cambiare la struttura dei Functor
combinati fra di loro, mentre la <$>
del Functor
no. Per questo Applicative
è più potente di Functor
. Ma la maggior potenza ha un prezzo. Nel caso di Haskell il prezzo è quello di avere codice meno prevedibile perchè potenzialmente più potente e libero. Banalizzando è un pó come usare una foldl
quando basta e avanza una map
, o usare codice in IO quando basta una funzione pura.
Ma come si comporta la <*>
nel caso della Fold
? Noi stiamo combinando queste due Fold
fra di loro:
sum = Fold (+) 0 id
genericLength = Fold (\n _ -> n + 1) 0 id
((/) <$> sum) <*> genericLength
dove gli elementi della Fold
sono al solito la funzione di step
, il valore iniziale e la funzione che trasforma l’ultimo step nel risultato finale della Fold
. La definizione di Applicative
per Fold
è:
instance Applicative (Fold a) where
pure b = Fold (\() _ -> ()) () (\() -> b)
(Fold stepL beginL doneL) <*> (Fold stepR beginR doneR) =
let step (Pair xL xR) a = Pair (stepL xL a) (stepR xR a)
begin = Pair beginL beginR
done (Pair xL xR) = (doneL xL) (doneR xR)
in Fold step begin done
Intanto importantissimo: la funzione a -> b
viene iniettata in una Fold
tramite pure
e nel nostro caso viene inserita come risultato finale, ignorando ogni altra computazione della Fold
. È quindi come una funzione che torna una costante, senza processare nessun elemento. Nel nostro caso la costante finale è una funzione.
Nel caso della combinazione <*>
, viene tornata una Fold
finale in cui lo stato iniziale è una Pair
con gli stati iniziali delle due Fold
usate come parametro, il passo di step processa un elemento della struttura Foldable
alla volta e lo passa alle due funzioni step dei due Fold
usati come parametro. Quindi usa lo stesso “trucco” che useremo se volessimo unire i due fold in una unica iterazione della lista, mantenendo una pair nello stato.
Lo stato finale della Fold
ha la forma (doneL xL) (doneR xR)
ed è in realtà una applicazione di funzione. Ovvero (doneL xL)
è una funzione. E la cosa ha senso, dato che abbiamo visto che possiamo creare un Fold (a -> b)
solo usando pure
e pure
inserisce la funzione nell’ultimo elemento della Fold
. Tornando all’esempio, doneL
è una funzione, nel nostro caso ((/) <$> sum)
torna nel risultato finale una funzione che accetta un valore Num b
e esegue la divisione con la sum
di tutta la lista. Alla funzione (doneL xL)
passiamo (doneR xR)
che nel nostro caso è genericLength
.
Torniamo alla nostra libreria MVC. All’inizio del tutto stava questa definizione di View
newtype View a = AsFold (FoldM IO a ())
Nel nostro caso una View
è vista come una funzione che accetta dati di tipo a
e può tornare una View
finale risultato della composizione di tutti i dati a
, lavorando nella Monad IO
. In particolare, la View
è anche un Monoid
:
> instance Monoid (View a) where
> mempty = AsFold mempty
> mappend (AsFold fold1) (AsFold fold2) = AsFold (mappend fold1 fold2)
Un Monoid
è una struttura definita come:
class Monoid a where
mempty :: a
mappend, (<>) :: a -> a -> a
e per la quale valgono la propietà associativa ed esiste un elemento neutro. I numeri Interi sono Monoid, e lo sono rispetto la somma o la moltiplicazione, tra le altre cose. Le liste sono Monoid e lo sono rispetto alla concatenazione.
Siccome una View è un Monoid, allora possiamo sempre combinare due View e ottenere una nuova View. Il che rende la View un tipo di dato molto comodo da usare e estremamente componibile. Come tutti i Monoid del resto.
Definire una Fold
come un Applicative
o Functor
o un Monoid
, permettere di ereditare tutte le propietà delle classi e tutte le funzioni di supporto. Si possono riusare i concetti tipici di un Functor
e di un Applicative
, senza doversene inventare di nuovi e ad-hoc. Lo stesso accade in matematica, quando in algebra (per non parlare della category theory) si definiscono le operazioni matematiche in base alla struttura che hanno, e si ereditano in automatico tutte le propietà e teoremi che valgono sulla struttura a cui sono state associate.
Se si studia http://hackage.haskell.org/package/foldl-1.0.7/docs/src/Control-Foldl.html si possono aprezzare le ulteriori definizioni “matematiche” legate alla Fold
, che si ereditano in automatico ogni volta che si definisce qualcosa in termini di Fold
.
Inoltre Functor
, Applicative
e Monoid
rispettano la propietà associativa e hanno un elemento neutro. Questo fa si che abbiano un comportamento facile da prevedere per chi programma e che siano facilmente componibili fra di loro.
Il codice Haskell d’alto livello (scritto da persone intelligenti e che sanno il fatto loro) è da una parte molto compatto, ma è anche purtroppo molto denso e difficile da capire, dato che le definizioni di nuove funzioni e strutture dati si riferiscono a concetti descritti in altri package, e che spesso non sono per niente banali. L’aproccio assomiglia alla costruzione di teorie matematiche in cui ogni definizione si rifà ai concetti e definizioni introdotte precedentenmente, e quindi è facile perdersi dopo un pó, se non si ripassano continuamente le definizioni precedenti.
Probabilmente le IDE dovrebbero aiutare di più permettendo di:
La buona notizia è che il riuso di codice Haskell è sicuramente superiore al riuso di codice object-oriented, dato che librerie Haskell che eseguono compiti complessi, hanno pochissime linee di codice, e riusano concetti definiti in altri packages, con una granularità di riuso molto più fine del tipico codice object-oriented.
]]>In fotografia spesso si decide di memorizzare le proprie foto digitali in un formato chiamato RAW, che possiamo pensare come un equivalente di un rullino di un po’ di tempo fa. I file RAW sono a tutti gli effetti i dati registrati dal sensore della macchina fotografica digitale.
Questi non rappresentano una vera e propria immagine, in quanto vanno interpretati da un processore che fa il rendering. Si preferisce quindi sempre affiancargli i file Jpeg già processati. Questi ci danno la certezza di essere visti nello stesso modo su tutti i dispositivi.
Le macchine fotografiche memorizzano la coppia Raw + Jpeg come due file separati con lo stesso nome nello stesso percorso del filesystem. Ad esempio nel caso di un file RAW di una macchina fotografica Fuji con estensione .RAF, i due file
DSC1234.RAF
DSC1234.JPG
sono a tutti gli effetti la stessa immagine, ma in due formati separati, e molto probabilmente risiedono nella stessa directory.
Avevo un po’ di file sparsi Raw + Jpeg nel disco rigido, e avevo bisogno un semplice script che mi permettesse di cercarli e copiarli in una directory. Un’ottima occasione per usare Haskell.
Questo file è scritto in Literate Haskell, questo vuol dire che è un sorgente eseguibile. Potete scaricare il codice sorgente qui:
e compilarlo utilizzando stack con il comando:
stack --resolver lts-3.15 --install-ghc runghc --package turtle 2015-11-30-Turtle-Raw.lhs
Questo ci permette di installare anche automaticamente la versione corretta di GHC sulla macchina.
Ogni file Haskell scritto nel 2015 inizia almeno con qualche estensione di GHC da importare:
> {-# LANGUAGE OverloadedStrings #-}
Turtle utilizza questa estensione di GHC per poter specificare le directory del filesystem come semplici stringhe.
> module Main where
La versione di FilePath di Turtle ha alcune funzioni differenti rispetto a quella del preludio, quindi va importata e nascosta quella originale.
> import Turtle
> import Filesystem.Path (addExtension)
> import Prelude hiding (FilePath)
Un file RAW nel mio caso ha una estensione RAF, quindi è una funzione che, presa una directory di origine, cerca tutti i file all’interno di quest’ultima.
> rafFiles :: FilePath -> Shell FilePath
> rafFiles source = find (suffix "RAF") source
Questo è equivalente a scrivere
find . -iname '*.raf'
In una shell Unix e produce uno stream di file, chiamato in Turtle Shell. Una volta che abbiamo il nostro file RAF possiamo assumere che ci sia una Jpeg equivalente con lo stesso nome, definiamo quindi una nuova funzione pura che rimuove l’estensione RAF e aggiunge l’estensione Jpeg in fondo.
> toJpeg :: FilePath -> FilePath
> toJpeg fp = addExtension baseName "JPG"
> where baseName = dropExtension fp
Shell è un funtore, quindi possiamo trasformarne i file interni usando fmap.
> equivalentJpegFiles :: FilePath -> Shell FilePath
> equivalentJpegFiles source = fmap toJpeg rafs
> where rafs = rafFiles source
A questo punto possiamo concatenare i nostri due stream di file per ottenere tutti i file che vogliamo spostare.
> allFiles :: FilePath -> Shell FilePath
> allFiles source = rafs <|> jpegs
> where jpegs = equivalentJpegFiles source
> rafs = rafFiles source
Per copiare i file, usiamo la primitiva di Turtle che prende il path di origine e di destinazione. Questa si chiama guardacaso cp e prende il path di origine e il path di destinazione, esattamente come
cp sourceFile destinationFile
Il filepath di destinazione ha lo stesso nome file concatenando la directory di destinazione utilizzando </>
> copy :: FilePath -> FilePath -> IO ()
> copy file destinationDir = cp file (destinationDir </> filename file)
E applichiamo questa funzione copy a tutto il nostro stream utilizzando liftIO
> copyAll :: FilePath -> FilePath -> Shell ()
> copyAll source target = do files <- allFiles source
> liftIO $ copy files target
A questo punto si tratta solo di ottenere la directory da cui partire per cercare le immagini e la directory di destinazione da linea di comando. Turtle ha un semplice DSL per definire i parametri di ingresso dello script che usa la sintassi dei funtori applicativi.
> args :: Parser (FilePath, FilePath)
> args = (,) <$> argPath "sourceDir" "The source directory where to find"
> <*> argPath "targetDirectory" "The directory to put the files"
Il nostro programma chiede le due directory di origine e destinazione dalla linea di comando ed esegue semplicemente la funzione copyAll.
> main :: IO ()
> main = do (source, target)
> <- options "find all raw + jpeg pairs and copy to target" args
> sh $ copyAll source target