Introduzione
Alcuni concetti possono risultare difficilmente intuibili o sembrare troppo astratti. Functor, Applicative e Monad vi sembreranno incredibilmente semplici dopo aver letto la traduzione di questo fantastico post!
Gli esempi sono scritti in Haskell, ma i concetti sono generici e possono essere applicati in vari ambiti.
Cominciamo!
Questo è un semplice valore:
E sappiamo come applicare una funzione a questo valore:
Molto semplice. Estendiamo questo concetto dicendo che un qualunque valore può trovarsi all’interno di un contesto. Per ora puoi immaginare un contesto come una scatola al cui interno puoi inserire un valore:
A questo punto, applicando una funzione a questo valore otterrai risultati
diversi a seconda del contesto. Questa è l’idea su cui Functor,
Applicative, Monad, Arrow, etc. sono basati. Il data type Maybe
definisce
due contesti correlati:
data Maybe a = Nothing | Just a
In un momento vedremo come l’applicazione di una funzione differisce quando
siamo all interno di Just a
piuttosto che di Nothing
. Prima parliamo dei Functor!
Functor
Quando un valore è all’interno di un contesto, non puoi applicarvi una normale funzione:
Questo è il momento per fmap
di entrare in azione. fmap
sa come
applicare funzioni a valori che si trovano all’interno di un contesto. Per esempio,
immagina di voler applicare (+3)
a Just 2
. Usa fmap
:
> fmap (+3) (Just 2)
Just 5
Bam! fmap
ci fa vedere come si fa! Ma come fa fmap
a sapere come applicare
la funzione?
Quindi cos’è un Functor?
Functor
è una typeclass.
Ecco la definizione:
1. per rendere un data type f un functor
2. quel data type deve definire come fmap
interagirà con lui
Un Functor
è un qualunque data type che definisce come fmap
deve
essere applicato a s` stesso. Ecco come funziona fmap
:
1. fmap
prende una funzione (come (+3)
)
2. e un functor (come Just 2
)
3. e ritorna un nuovo functor (come Just 5
)
Questo ci permette di fare:
> fmap (+3) (Just 2)
Just 5
E fmap
magicamente applica questa funzione, dato che Maybe
e’ un Functor.
Specifica come fmap
si applica ai Just
e ai Nothing
.
instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
Ecco quello che succede dietro le scene quando scriviamo fmap (+3) (Just 2)
:
1. scarta il valore dal contesto
2. applica la funzione
3. reincarta il valore nel contesto
Cosa succede se chiediamo a fmap
di applicare (+3)
a Nothing
?
1. nessun valore
2. non applicare la funzione
3. il risultato è Nothing
> fmap (+3) Nothing
Nothing
Come Morpheus in Matrix, fmap
semplicemente sa cosa fare; Nothing
entra e
Nothing
esce! fmap
è zen. A questo punto l’esistenza del data type
Maybe
ha un senso. Per esempio, ecco come lavoreresti con un record di un
database in un linguaggio che non ha Maybe
:
post = Post.find_by_id(1)
if post
return post.title
else
return nil
end
Ma in Haskell:
fmap (getPostTitle) (findPost 1)
Se findPost
trova un post, otterremo il titolo con getPostTitle
. Se ritorna
Nothing
, il risultato finale sarà Nothing
! Interessante, no?
<$>
` la versione infix di fmap
, per cui spesso vedrai la versione
equivalente:
getPostTitle <$> (findPost 1)
Ecco un altro esempio: cosa succede quando applichi una funzione a una lista?
1. un array di valori
2. applica la funzione a ciascun valore
3. un nuovo array di valori
Anche le liste sono Functor! Ecco la definizione:
instance Functor [] where
fmap = map
Okay, un ultimo esempio: cosa succede quando applichi una funzione ad un’altra funzione?
fmap (+3) (+3)
Questa e’ una funzione:
Questa è una funzione applicata ad un’altra funzione:
Il risultato è un’altra funzione!
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
Quindi anche le funzioni sono Functor!
instance Functor ((->) r) where
fmap f g = f . g
Quando usi fmap
su una funzione, semplicemente stai componendo due funzioni!
Applicative
A breve…