Indice
Hug + Elm = fun
Prendi una libreria strettamente Python 3, sì, hai letto bene, la “versione di sviluppo” di Python 1:

ed un linguaggio funzionale compilato verso JavaScript e cosa ottieni?

Sicuramente una ventata di freschezza e tanto tanto type checking.
Python è un linguaggio agile, è facile prototipare, ma con un po’ di pratica è anche possibile creare progetti che sfidano gli anni 2, scegliendo i propri limiti invece di trovarseli imposti dal linguaggio. Questa libertà ha però un prezzo, in particolare i refactoring sono sempre un rischio perché il compilatore non fa praticamente niente più che trovare gli errori di sintassi e tutto il resto avviene a runtime. Per questo è obbligatorio avere una buona copertura di test ed è sempre buona prassi “blindare” la parte di codice da modificare con ulteriori test prima di affrontare qualsiasi modifica non banale.
Avendo giocato un po’ con OCaml e Rust a volte mi manca l’aiuto del compilatore quando scrivo in Python, spesso davvero l’errore che era sfuggito nel codice è poco più che un errore di sintassi.
Ad esempio quante volte nello scrivere un algoritmo ho deciso che un set
era
una struttura dati più adatta di una list
? Tante, e quante le volte che nel
rinominare data.append(x)
in data.add(x)
me ne sono scordato uno in un
remoto if
che ha dato errore solo grazie ai test? Tante, e quante invece
l’errore è saltato fuori solo usando il programma? Meno per fortuna, ma capita.
Ma non dovrebbe, nel 2016 mi aspetto di più da un linguaggio… ed infatti si può avere di più!
Python dalla versione 3.0 accetta delle annotazioni ad argomenti e valori di
ritorno delle funzioni 3, queste annotazioni sono libere ma in Python 3.5 è
stato introdotto il modulo typing
e sono state standardizzate le annotazioni
di tipo. Ad oggi non esiste un type checker integrato in Python, ma il progetto
mypy, da cui è partito tutto, oggi vede come
ultimo committer il nostro Guido!

Questo mi da ottime speranze per il futuro, perché ad oggi mypy4 non mi è stato molto utile, dato che solleva una valanga di errori nel controllare le dipendenze del mio mini progetto.
Ma con un piccolo file di esempio invece l’errore che ci aspettiamo compare subito:
1
def
demo
(
num
:
int
,
a
:
set
):
2
if
num
%
5
:
3
a
.
add
(
num
)
4
# some other code here.
5
# ...
6
# far far away.
7
else
:
8
a
.
append
(
num
*
2
)
9
return
a
1
$ mypy typedemo.py
2
typedemo.py: note: In function
"demo"
:
3
typedemo.py:12: error: Set[
Any]
has no attribute "append"
Per chi preferisce una soluzione più integrata, una IDE come PyCharm già integra un type checker ed in tempo record ha cominciato a supportare la neonata sintassi:

Quindi possiamo sperare che presto anche in Python tanti errori banali potranno essere individuati a “check-time” invece che durante i test o peggio durante l’uso.
Ma in realtà Python in questo tipo di controlli è un novellino, c’è un
linguaggio che ha fatto degli errori di compilazione per essere umani il suo cavallo
di battaglia. Elm è un linguaggio funzionale che si compila
in JavaScript, il “traduttore” è scritto in Haskell. Non temete, di fatto non vi
capiterà spesso di doverlo compilare, perché viene distribuito come pacchetto
binario per npm
.
Nato come libreria per scrivere giochi, si è evoluto negli ultimi anni come un linguaggio completo per la scrittura di fontend complessi, senza niente da invidiare a framework più famosi, dato che integra tutte le buzzword del momento: virtual-dom, functional reactive programming, time-travelling debugger, e la più importante no runtime exceptions.
Come è possibile non avere eccezioni a runtime? Per prima cosa non devono
esistere i tipi nullabili, niente più null
o None
su cui chiamare metodi o
da passare a funzioni che si aspettano un valore concreto, se una variabile può
non avere un valore dovremo definirla come Maybe
oppure Optional
in altri
linguaggi.
Un Maybe <tipo a>
rappresenta una opzione tra Just <valore di tipo a>
e
Nothing
. Questo da solo non è molto diverso da come facciamo spesso in
python, cioè usare implicitamente None
per codificare l’assenza di un
valore. Però dato che in python questa modifica è implicita, non sapremo
mai da una visione parziale del codice se il valore None
è previsto
come caso possibile oppure no.
Quindi per completare il puzzle è necessario un pattern matching esaustivo,
Vediamo come definire un Maybe
in Elm e come il compilatore ci aiuta nel
gestire esaustivamente tutti i casi che potrebbero manifestarsi a runtime.
1
>
var
=
Just
123
2
Just
123
:
Maybe
.
Maybe
number
Abbiamo definito la variabile var
come un Just <numero>
.
Just
non è altro che una delle due varianti del tipo Maybe
, definito come:
1
type
Maybe
a
=
Nothing
|
Just
a
Grazie all’inferenza di tipi, anche se in questo caso non abbiamo definito il tipo
di var
, questo viene dedotto dal letterale 123 : number
e quindi var
assume il tipo
var : Maybe number
, in particolare questa istanza ha valore Just 123
.
A questo punto per accedere al numero contenuto useremo il pattern matching:
1
>
case
var
of
\
2
|
Just
num
->
num
ma in questo caso ci siamo scordati di gestire il caso Nothing
in cui il valore
non è presente (l’esempio è minimale, ma immaginate l’accesso per chiave ad un
dizionario che può non contenere la chiave).
Questo codice è incompleto, num
non è definito se var
è Nothing
, e fino
alla scorsa versione di Elm avremmo dovuto aspettare il momento dell’esecuzione
per scoprire che un ramo del match non era coperto, ma dall’ultima versione il
compilatore è diventato più “intelligente” e ci fa notare subito il problema.
1
============================== ERRORS ==============================
2
3
-- MISSING PATTERNS ------------------------------ repl-temp-000.elm
4
5
This `case` does not have branches for all possibilities.
6
7
4│> case var of
8
5│> Just num -> num
9
10
You need to account for the following values:
11
12
Maybe.Nothing
13
14
Add a branch to cover this pattern!
Ma fa anche altro, se volete un assaggio vi consiglio di guardare il blog post Compilers as Assistants dell’autore di Elm, Evan Czaplicki.
Con questi fondamenti (e se siete dei teorici dei linguaggi potrete elencare gli altri requisiti) e con un passaggio controllato per accedere al mondo usafe del JavaScript, abbiamo un linguaggio coerente per scrivere codice che girerà su una VM JavaScript senza scrivere mai un rigo di JavaScript.
Lo so, JavaScript non è un brutto linguaggio, non peggio di altri, ma non ho particolare interesse ad imparare un lunguaggio che assomiglia molto a Python come linguaggio dinamico e amichevolmente anarchico. Però se voglio fare qualcosa che sia compatibile col presente, web 2.0, se-non-c’è-la-web-app-non-esiste ho l’ottima opportunità di usare JavaScript solo come linguaggio target per un compilatore.
L’introduzione si è protratta fin troppo, passiamo all’implementazione.
Per questo esperimento mi sono preso la libertà di usare framework non mainstream: - per le API ho scelto HUG, - a sua volta HUG usa falcon, un framework WSGI minimale, - invece come ORM ho scelto peewee.
Il backend della nostra app «Guess the number» ha 2 endpoint dinamici:
-
POST /play
per iniziare una nuova partita, conid
univoco etoken
random associato, -
PUT /game/{gameid}/guess/{guess}
per cercare di indovinare un numero.
Oltre a questi il backend serve il file statici index.html
e l’app JS, elm.js
.
Potete usare un workspace cloud9 per fare le prove, clonando quello che ho configurato nello scrivere questo post: ide.c9.io/naufraghi/guess