2014-05-02 5 views
7

Non riesco a utilizzare la libreria lens per un problema particolare. Sto cercando di passareUtilizzo di un obiettivo due volte

  1. una struttura di dati aggiornati
  2. una lente concentrata sulla parte di quella struttura aggiornata

a un'altra funzione, g. Passo sia la lente che la struttura dati perché lo g ha bisogno di alcune informazioni condivise dalla struttura dati e un'informazione. (Se aiuta, la struttura dati contiene informazioni su una distribuzione di probabilità congiunta, ma lo g funziona solo marginalmente e ha bisogno di sapere quale marginale sto guardando. L'unica differenza tra i due marginali è la loro media con il resto della loro definizione condivisa nella struttura dei dati).

Il mio primo tentativo si presentava così

f :: Functor f => Params -> ((Double -> f Double) -> Params -> f Params) -> a 
f p l = g (l %~ upd $ p) l 
    where upd = ... 

g p x = go p p^.x 

ma che non riesce durante la compilazione perché f ottiene dedurre come Identity per l'aggiornamento e Const Double per il getter.

Qual è il modo migliore per realizzare ciò che voglio fare? Posso immaginare di essere in grado di fare una delle seguenti operazioni:

  1. eseguire una copia della lente in modo che il tipo di inferenza può essere diverso in ciascun caso
  2. anziché passare la struttura aggiornata e la lente, passo la struttura originale e una lente che restituisce un valore modificato (se voglio solo aggiornare la parte della struttura che la lente guarda).
  3. fare una scelta migliore progettazione per la mia struttura di funzioni/dati
  4. qualcosa di completamente diverso

Grazie per qualsiasi aiuto!

risposta

9

András Kovács answer mostra come ottenere ciò con RankNTypes.Se si desidera evitare RankNTypes, quindi è possibile utilizzare ALens e cloneLens:

f :: a -> ALens' a Int -> (Int, a) 
f a l = (newvalue, a & cloneLens l .~ newvalue) 
    where oldvalue = a^.cloneLens l 
     newvalue = if oldvalue == 0 then 0 else oldvalue - 1 

Control.Lens.Loupe fornisce agli operatori e le funzioni che si occupano di ALens invece di Lens.

nota che in molti casi, si dovrebbe anche essere in grado di utilizzare <<%~, che è come %~ ma anche restituisce il vecchio valore, o <%~, che restituisce il nuovo valore:

f :: a -> LensLike' ((,) Int) a Int -> (Int, a) 
f a l = a & l <%~ g 
    where g oldvalue = if oldvalue == 0 then 0 else oldvalue - 1 

Questo ha il vantaggio che può funzionare anche con Isos o talvolta anche con Traversals (quando il tipo di destinazione è un Monoid).

7

volete che il vostro firma di tipo a guardare come questo:

f :: Params -> Lens Params Params Double Double -> ... 
-- alternatively, instead of the long Lens form you can write 
-- Lens' Params Double 

Questo non è equivalente a quello che hai scritto nella firma, perché il parametro funtore viene quantificata all'interno Lens:

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t) 

La firma corretta si traduce in:

f :: Params -> (forall f. Functor f => (Double -> f Double) -> Params -> f Params) -> ... 

Thi s impedisce al compilatore di unificare i diversi parametri f di diversi obiettivi, i. e. puoi usare l'obiettivo polimorficamente. Nota che hai bisogno dell'estensione GHC RankNTypes o Rank2Types per poter scrivere la firma.

2

Benno ha fornito la migliore risposta generica.

Ci sono due altre opzioni, tuttavia, che offro qui per completezza.

1.)

Ci sono diversi Loupe combinatori in Lens.

http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Loupe.html

tutti hanno nomi che coinvolgono #.

^# e #%= entrambi prendono ALens che è una lente istanziata a una particolare scelta concreta di funtore.

Questo può essere utile se è necessario passare attorno agli elenchi di obiettivi o se realmente si richiedono realmente più pass.

2.)

Un'altra opzione, e la mia tattica preferita, è di capire come fare entrambe le operazioni un tempo stesso.

Qui si modifica, ma si desidera il valore impostato. Bene, si può darti che usando <%~ invece di %~.

Ora basta istanziare l'obiettivo con una sola scelta di funtore e il codice diventa più veloce.