Il tipo (->) r a
è solo un alias per r -> a
, niente di più. Il suo tipo è *
("tipo").
Il costruttore di tipi (->) r
può essere pensato come r -> ___
dove r
è già noto ma ___
deve essere compilato in seguito. Il suo tipo è * -> *
("tipo costruttore").
Il costruttore di tipo (->) r
è esso stesso parametrizzato dal tipo di input r
. Per ridurre il rischio di confusione, potrebbe essere utile correggere r
in qualcosa di concreto. Quindi per il resto di questa risposta, sceglierò r = Int
. La spiegazione seguente funzionerà per qualsiasi scelta di r
.
Sia i funtori applicativi che i funtori classificano costruttori di tipi , non tipi :
- Diciamo che
[___]
(list constructor) è un functor, Int -> ___
(le funzioni che aspettano un intero) è un functor, IO ___
è un functor, Maybe ___
è un functor, ecc.
- Facciamo non diciamo
[Int]
(lista di interi) è un functor, né diciamo Int -> String
(le funzioni aspettano un intero e restituiscono una stringa) è un functor, né IO ()
, né Maybe String
, ecc. (Naturalmente, nella scrittura informale le persone lo dicono per sciatteria, ma mentre imparano è importante tenere a mente questa distinzione.)
pure :: a -> f a -- general type, where f is Applicative
pure :: a -> (->) Int a -- specialized type for f = ((->) Int)
pure :: a -> (Int -> a) -- just another way to write the above
pure x = (\_ -> x)
La funzione pure
crea una funzione che restituirà a
, ignorando qualsiasi percentuale diInt
che riceverà. Fornisce un modo per "avvolgere" le cose in un funtore applicativo.
Come esempio concreto, se scrivi pure "hi"
, viene creata una funzione \_ -> "hi"
che restituisce sempre "hi"
indipendentemente dall'input che ottiene. Per riavere il tuo "hi"
, applica il risultato a qualsiasi numero intero, "estraendo" il functor applicativo:
>>> (pure "hi") 42
"hi"
(<*>) :: f (a -> b) -> f a -> f b -- general type, where f is Applicative
(<*>) :: (->) Int (a -> b) -> (->) Int a -> (->) Int b -- specialized type for f = ((->) Int)
(<*>) :: (Int -> (a -> b)) -> (Int -> a) -> (Int -> b) -- just another way to write the above
f <*> g = \x -> f x (g x)
La funzione <*>
ha due funzioni:
- La prima funzione
f
restituirà una funzione a -> b
in cambio di un Int
.
- La seconda funzione
g
restituirà un valore a
in cambio di un Int
.
L'obiettivo di <*>
è di creare una nuova funzione che produca qualche valore b
in cambio di un Int
. Esiste un modo "ovvio" per giocare a questo gioco: quando in futuro otterrai Int
, lo invierai a f
e g
e in cambio ti forniranno a -> b
e a
. Quindi puoi applicare a -> b
a a
per ottenere b
, che raggiunge l'obiettivo.
Ecco un esempio concreto: pure (+) <*> pure 2.0 <*> pure 3.0
. Questa espressione crea una funzione che restituisce sempre 5.0
in cambio di qualsiasi percentuale diInt
che dai. Come al solito puoi "scartarlo" applicandolo a qualche Int
:
>>> (pure (+) <*> pure 2.0 <*> pure 3.0) 42
5.0
Ma finora, nessuno di questi esempi è stato molto interessante, perché si limitano a "avvolgere" le cose usando pure
, che ignora sempre l'argomento. Ecco uno più interessante:
>>> let get input = input
>>> (pure (-) <*> get <*> pure 7) 42
35
>>> (pure (-) <*> get <*> pure 7) 10
3
Rispetto agli esempi precedenti, la funzione get
è piuttosto insolita in quanto fa effettivamente ispeziona l'argomento Int
, restituendo esattamente ciò che vede. Ciò significa che il risultato di questa catena di calcoli varia a seconda dell'argomento che riceve alla fine.
pure (-) <*> get <*> pure 7
≡ (\input -> (-)) <*> (\input -> input) <*> (\input -> 7)
≡ (\input -> (-) input) <*> (\input -> 7)
≡ (\input -> (-) input 7)
≡ (\input -> input - 7)
In effetti, se guardi l'espressione applicativa originale e fai uno strappo un po 'per ignorare pure
e <*>
, puoi leggerlo direttamente come
(-) INPUT 7
≡ INPUT - 7
dove INPUT
rappresenta il futuro valore Int
.
Ora considera il tuo esempio:
(+) <$> (*2) <*> (+7)
≡ (+) <$> (\input -> input * 2) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2)) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2) (input + 7))
≡ (\input -> (input * 2) + (input + 7))
Informalmente questo descrive il seguente calcolo:
(+) (INPUT * 2) (INPUT + 7)
≡ (INPUT * 2) + (INPUT + 7)
Ad esempio, se l'input è 42, dovresti aspettarti 133 come risultato:
>>> ((+) <$> (*2) <*> (+7)) 42
133
Lo scopo del functor applicativo ((->) Int)
per consentire al programmatore di separare la logica che dipende dall'argomento di input dal codice effettivo che fornisce il valore concreto. In altre parole, nel codice reale probabilmente si costruisce un'espressione applicativa piuttosto ampia e complicata, senza sapere quale sarà l'input. Non dovresti scartare subito il valore applicativo, ma probabilmente un posto lontano nel codice (forse in un altro progetto completamente!).