La lettura corretta di r ^. responseStatus . statusCode
è r ^. (responseStatus . statusCode)
. Questo è naturale, poiché la funzione function restituisce una funzione quando viene applicata a due argomenti, quindi (r ^. responseStatus) . statusCode
deve restituire una funzione, al contrario di qualsiasi valore che potrebbe essere stampato.
Questo lascia ancora aperta la domanda per cui le lenti si compongono nell'ordine "sbagliato". Poiché l'implementazione degli obiettivi è un po 'magica, vediamo un esempio più semplice.
first
è una funzione che mappa il primo elemento di una coppia:
first :: (a -> b) -> (a, c) -> (b, c)
first f (a, b) = (f a, b)
Cosa map . first
fare? first
prende una funzione che agisce sul primo elemento, e restituisce una funzione che agisce su una coppia, che è più evidente se si parenthesize il tipo in questo modo:
first :: (a -> b) -> ((a, c) -> (b, c))
Inoltre, ricordare il tipo di map
:
map :: (a -> b) -> ([a] -> [b])
map
accetta una funzione che agisce su un elemento e restituisce una funzione che agisce su un elenco. Ora, f . g
funziona applicando prima g
e quindi inserendo il risultato in f
.Quindi, map . first
prende una funzione che agisce su qualche tipo di elemento, la converte in una funzione che agisce su coppie, quindi la converte in una funzione che agisce su elenchi di coppie.
(map . first) :: (a -> b) -> [(a, c)] -> [(b, c)]
first
e map
sia attivare funzioni che agiscono su una parte di una struttura di funzioni che agiscono su tutta la struttura. In map . first
, qual è l'intera struttura per first
diventa lo stato attivo per map
.
(map . first) (+10) [(0, 2), (3, 4)] == [(10, 2), (13, 4)]
ora uno sguardo al tipo di lenti:
type Lens = forall f. Functor f => (a -> f b) -> (s -> f t)
cercate di ignorare le Functor
bit per ora. Se si strizza leggermente, questo è simile ai tipi di map
e first
. E succede così che gli obiettivi convertano anche le funzioni che agiscono su parti di strutture in funzioni che agiscono su intere strutture. Nella firma sopra s
denota l'intera struttura e a
ne indica una parte. Poiché la nostra funzione di input può modificare il tipo di a
su a -> f b
(come indicato da a -> f b
), è necessario anche il parametro t
, che significa "il tipo di s
dopo aver modificato a
in b
al suo interno".
statusCode
è una lente che converte una funzione che agisce su un Int
a una funzione che agisce su un Status
:
statusCode :: Functor f => (Int -> f Int) -> (Status -> f Status)
responseStatus
converte una funzione che agisce su un Status
a una funzione agisce su un Response
:
responseStatus :: Functor f => (Status -> f Status) -> (Response -> f Response)
Il tipo di responseStatus . statusCode
segue lo stesso schema che abbiamo visto con map . first
:
responseStatus . statusCode :: Functor f => (Int -> f Int) -> (Response -> f Response)
Resta da vedere come esattamente funziona ^.
. È intimamente legato alla meccanica di base e alla magia delle lenti; Non lo ripeterò qui, dato che ci sono parecchi scritti a riguardo. Per un'introduzione, ti consiglio di guardare this one e this one e potresti anche guardare this excellent video.
'statusCode' deve essere un obiettivo, non il selettore del campo di registrazione. Immagino che debbano aver nascosto il selettore di campo ed esportato una lente con lo stesso nome; abbastanza confuso se me lo chiedi. (O ha scritto un'istanza Show personalizzata.) –
@ReidBarton Deve essere una parte del puzzle. Dato che conosco molto poco delle lenti, la mia domanda è anche una domanda sull'idea alla base: come utilizzare la normale composizione delle funzioni per accedere alla struttura in ordine inverso? (Questo mi ricorda un po 'lo stile di passaggio di continuazione.) –
Questo è sostanzialmente corretto. Le lenti sono un po 'come i calcoli CPS di un tipo quindi, come effetto collaterale, la composizione della funzione flip. –