11

Ho letto A wreq tutorial:Come si possono implementare gli "obiettivi compositivi che usano la composizione delle funzioni" di Haskell con questo strano ordine di argomenti?

Una lente fornisce un modo per concentrarsi su una parte di un valore di Haskell. Per esempio , il tipo Response ha un obiettivo responseStatus, che si concentra sulle informazioni di stato restituite dal server.

ghci> r ^. responseStatus 
Status {statusCode = 200, statusMessage = "OK"} 

L'operatore ^. assume un valore come primo argomento, una lente come seconda, e restituisce la porzione del valore focalizzata sulla dall'obiettivo.

Componiamo obiettivi utilizzando la funzione di composizione, che ci consente di concentrarci facilmente su una parte di una struttura profondamente annidata su .

ghci> r ^. responseStatus . statusCode 
200 

non può venire con un modo come composizione funzione fatto con questo ordine degli argomenti potrebbe trattare la struttura nesting in questo ordine.

Aspetto: r ^. responseStatus . statusCode potrebbe essere r ^. (responseStatus . statusCode) o (r ^. responseStatus) . statusCode.

Nel primo si dice costruiamo una funzione che primi tratta statusCode (ottiene dal record di Status -? Come posso dedurre dal valore indicato Status {statusCode = 200, statusMessage = "OK"}), e poi passa a responseStatus che deve trattare la stato della risposta. Quindi, è il contrario: in realtà, il codice di stato è una parte dello stato di risposta.

Anche la seconda lettura non ha senso perché tratta anche il codice di stato.

+3

'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.) –

+0

@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.) –

+3

Questo è sostanzialmente corretto. Le lenti sono un po 'come i calcoli CPS di un tipo quindi, come effetto collaterale, la composizione della funzione flip. –

risposta

12

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.

Problemi correlati