Per rispettare i lettori veloci, inizio prima con una definizione precisa, proseguo con una rapida spiegazione più "semplice" in inglese, quindi passo agli esempi.
Ecco una definizione sia concisa che precisa leggermente riformulata:
Una monade (in informatica) è formalmente una mappa che:
invia ogni tipo X
di un determinato linguaggio di programmazione a un nuovo tipo T(X)
(chiamato "tipo di T
-computer con valori in X
");
dotato di una regola per comporre due funzioni della forma
f:X->T(Y)
e g:Y->T(Z)
una funzione g∘f:X->T(Z)
;
in modo associativo in senso evidente e unitario rispetto a una determinata funzione unitaria chiamata pure_X:X->T(X)
, da considerare come valore per il calcolo puro che semplicemente restituisce quel valore.
Quindi, in parole semplici, una monade è una regola per passare da qualsiasi tipo X
a un altro tipoT(X)
e una regola per passare da due funzioni f:X->T(Y)
e g:Y->T(Z)
(che si desidera comporre ma non è possibile) a una nuova funzioneh:X->T(Z)
. Che, tuttavia, non è la composizione in senso matematico rigoroso. Fondamentalmente stiamo "piegando" la composizione della funzione o ridefinendo il modo in cui le funzioni sono composte.
Inoltre, abbiamo bisogno della regola di composizione della monade per soddisfare gli "ovvi" assiomi matematici:
- Associatività : comporre
f
con g
e poi con h
(dall'esterno) dovrebbe essere lo stesso che comporre g
con h
e poi con f
(dall'interno).
- Proprietà unitaria : la composizione
f
con la funzione identità su entrambi i lati dovrebbe produrre f
.
Ancora una volta, in parole semplici, non possiamo semplicemente impazzire ridefinendo la composizione della nostra funzione come ci piace:
- Abbiamo prima bisogno dell'associatività per essere in grado di comporre diverse funzioni di seguito
f(g(h(k(x)))
, ad esempio , e di non preoccuparci di specificare l'ordine delle coppie di funzioni di composizione. Dato che la regola della monade prescrive solo come comporre una coppia di funzioni , senza quell'assioma, dovremmo sapere quale coppia è composta per prima e così via. (Si noti che è diverso dalla proprietà di commutatività che ha f
composto g
erano gli stessi di g
composti con f
, che non è richiesto).
- E in secondo luogo, abbiamo bisogno della proprietà unitaria, che è semplicemente per dire che le identità compongono banalmente il modo in cui le aspettiamo. In questo modo possiamo refactoring in modo sicuro ogni volta che tali identità possono essere estratte.
Quindi di nuovo in breve: una monade è la regola dell'estensione del tipo e delle funzioni compositive che soddisfano i due assiomi: associatività e proprietà unitaria.
In termini pratici, vuoi che la monade sia implementata per te dal linguaggio, dal compilatore o dal framework che si occuperà di comporre le funzioni per te. Quindi puoi concentrarti sulla scrittura della logica della tua funzione piuttosto che preoccuparti di come viene implementata la loro esecuzione.
Questo è essenzialmente, in poche parole.
Essendo matematico professionista, preferisco evitare di chiamare h
la "composizione" di f
e g
. Perché matematicamente non lo è. Chiamarla "composizione" erroneamente presuppone che h
sia la vera composizione matematica, cosa che non lo è. Non è nemmeno determinato in modo univoco da f
e g
. Invece, è il risultato della nuova "regola di composizione" della nostra monade delle funzioni. Che può essere totalmente diverso dall'effettiva composizione matematica anche se quest'ultima esiste!
Per renderlo meno asciutto, fammi provare ad illustrarlo con l'esempio che sto annotando con piccole sezioni, in modo da poter saltare direttamente al punto.
Eccezione come esempi Monade
Supponiamo di voler comporre due funzioni:
f: x -> 1 / x
g: y -> 2 * y
Ma f(0)
non è definito, quindi e
viene generata un'eccezione . Quindi come puoi definire il valore compositivo g(f(0))
? Lancia di nuovo un'eccezione, ovviamente! Forse lo stesso e
. Forse una nuova eccezione aggiornata e1
.
Cosa succede esattamente qui? Innanzitutto, abbiamo bisogno di nuovi valori di eccezione (diversi o uguali). È possibile chiamare nothing
o null
o qualsiasi altra cosa, ma l'essenza rimane la stessa - dovrebbero essere i nuovi valori, ad esempio, non dovrebbe essere una number
nel nostro esempio qui. Preferisco non chiamarli null
per evitare confusione con come null
può essere implementato in qualsiasi lingua specifica. Allo stesso modo preferisco evitare nothing
perché è spesso associato null
, il che, in linea di principio, è ciò che null
dovrebbe fare, tuttavia, tale principio viene spesso piegato per qualsiasi motivo pratico.
Che cos'è esattamente l'eccezione?
Questa è una cosa banale per qualsiasi programmatore esperto, ma vorrei far cadere alcune parole solo per estinguere qualsiasi worm di confusione:
L'eccezione è un oggetto che incapsula informazioni su come si è verificato il risultato non valido dell'esecuzione.
Questo può variare dal buttare via tutti i dettagli e restituire un singolo valore globale (come NaN
o null
) o generare un lungo elenco di registri o cosa è successo esattamente, inviarlo a un database e replicare su tutto il livello di archiviazione dei dati distribuiti;)
La differenza importante tra questi due esempi estremi di eccezione è che nel primo caso non ci sono effetti collaterali . Nel secondo ci sono. Il che ci porta alla domanda (da mille dollari):
Sono ammesse eccezioni nelle funzioni pure?
Risposta più breve : Sì, ma solo quando non portano a effetti collaterali.
Risposta più lunga. Per essere puri, l'output della tua funzione deve essere determinato in modo univoco dal suo input. Quindi modifichiamo la nostra funzione f
inviando 0
al nuovo valore astratto e
che chiamiamo eccezione. Ci assicuriamo che il valore e
non contenga informazioni esterne che non siano determinate in modo univoco dal nostro input, ovvero x
. Quindi, ecco un esempio di eccezione senza effetti collaterali:
e = {
type: error,
message: 'I got error trying to divide 1 by 0'
}
Ed eccone uno con effetti collaterali:
e = {
type: error,
message: 'Our committee to decide what is 1/0 is currently away'
}
In realtà, ha effetti collaterali solo se quel messaggio può cambiare in futuro. Ma se è garantito che non cambierà mai, quel valore diventa unicamente prevedibile, e quindi non ci sono effetti collaterali.
Per renderlo ancora più sciocco. Una funzione che ritorna 42
mai è chiaramente pura. Ma se qualcuno pazzo decide di creare 42
una variabile che il valore potrebbe cambiare, la stessa funzione smette di essere pura nelle nuove condizioni.
Si noti che sto usando l'oggetto notazione letterale per semplicità per dimostrare l'essenza. Sfortunatamente le cose sono incasinate in linguaggi come JavaScript, dove error
non c'è un tipo che si comporti come vogliamo qui rispetto alla composizione delle funzioni, mentre i tipi reali gradiscono null
o NaN
non si comportano in questo modo ma piuttosto passano attraverso alcuni artificiali e non sempre intuitivi digitare conversioni.
Digita estensione
Poiché vogliamo variare il messaggio all'interno della nostra eccezione, dichiariamo davvero un nuovo tipo E
per l'intero oggetto eccezione e quindi Questo è ciò che maybe number
fa, a parte il suo nome confuso, che deve essere di tipo number
o del nuovo tipo di eccezione E
, quindi è davvero l'unione number | E
di number
e E
. In particolare, dipende da come vogliamo costruire E
, che non è né suggerito né riflesso nel nome maybe number
.
Cos'è la composizione funzionale?
È le funzioni matematiche funzionamento taking
f: X -> Y
ed g: Y -> Z
e costruire la loro composizione in funzione h: X -> Z
soddisfacente h(x) = g(f(x))
. Il problema con questa definizione si verifica quando il risultato f(x)
non è consentito come argomento di g
.
In matematica queste funzioni non possono essere composte senza lavoro extra. La soluzione rigorosamente matematica per il nostro esempio sopra di f
e g
è quella di rimuovere 0
dall'insieme di definizione di f
. Con quel nuovo insieme di definizioni (nuovo tipo più restrittivo di x
), f
diventa componibile con g
.
Tuttavia, non è molto pratico in programmazione limitare l'insieme della definizione di f
simile. Invece, è possibile utilizzare le eccezioni.
O come un altro approccio, valori artificiali vengono create come NaN
, undefined
, null
, Infinity
ecc Quindi valutate 1/0
per Infinity
e 1/-0
per -Infinity
. E quindi forzare il nuovo valore nella tua espressione invece di generare un'eccezione. Portando a risultati che potresti trovare o meno prevedibili:
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
E torniamo ai numeri regolari pronti per andare avanti;)
JavaScript ci consente di continuare a eseguire espressioni numeriche a qualsiasi costo senza generare errori come nell'esempio sopra. Ciò significa che consente anche di comporre funzioni. Il che è esattamente ciò di cui parla la monade: è una regola comporre funzioni che soddisfino gli assiomi come definiti all'inizio di questa risposta.
Ma la regola di comporre la funzione, derivante dall'implementazione di JavaScript per gestire gli errori numerici, è una monade?
Per rispondere a questa domanda, tutto ciò che serve è controllare gli assiomi (lasciato come esercizio fisico come non parte della domanda qui;).
L'eccezione di lancio può essere usata per costruire una monade?
In effetti, una monade più utile sarebbe invece la regola che prescrive che se f
genera un'eccezione per alcuni x
, così fa la sua composizione con qualsiasi g
. Inoltre, rendono l'eccezione E
globalmente unica con un solo valore possibile in assoluto ( oggetto terminale nella teoria delle categorie). Ora i due assiomi sono immediatamente controllabili e otteniamo una monade molto utile. E il risultato è ciò che è noto come forse monade .