2014-09-21 29 views
6

Per cercare di semplificare questo problema ho definito queste funzioni di direzione:Esistono utili astrazioni per la sintassi del record di Haskell?

splitA :: (Arrow arr) => arr a b -> arr a (b,a) 
splitA ar = ar &&& (arr (\a -> id a)) 

recordArrow 
    :: (Arrow arr) 
    => (d -> r) 
    -> (d -> r -> d) 
    -> (r -> r) 
    -> arr d d 
recordArrow g s f = splitA (arr g >>^ f) >>^ \(r,d) -> s d r 

Che poi cerchiamo di fare a me una cosa del genere:

unarrow :: ((->) b c) -> (b -> c) -- unneeded as pointed out to me in the comments 
unarrow g = g 


data Testdata = Testdata { record1::Int,record2::Int,record3::Int } 

testRecord = unarrow $ 
     recordArrow record1 (\d r -> d { record1 = r }) id 
    >>> recordArrow record2 (\d r -> d { record2 = r }) id 
    >>> recordArrow record3 (\d r -> d { record3 = r }) id 

Come si può vedere questo non fa buon uso di ASCIUTTO.

Spero ci possa essere una sorta di estensione del linguaggio che potrebbe aiutare a semplificare questo processo. In modo che il sopra potrebbe semplicemente essere riscritto come:

testRecord' = unarrow $ 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

Aggiornato per chiarezza: Per chiarire un po '. Sono consapevole che potrei fare qualcosa del genere:

foo d = d { record1 = id (record1 d), record2 = id (record2 d) } 

Ma questo ignora qualsiasi ordine di esecuzione e qualsiasi stato. Supponiamo che la funzione di aggiornamento per record2 si basi sul valore aggiornato per record1. In alternativa, potrei voler creare una freccia diversa simile a questa: arr d (d,x) e quindi voglio creare un elenco di [x] il cui ordine dipende dall'ordine di valutazione dei record.

Quello che ho trovato è che spesso desidero eseguire alcune funzionalità e successivamente aggiornare un record. Posso farlo facendo passare lo stato come questo

g :: d -> r -> d 
foo d = let d' = d { record1 = (g d) (record1 d) } in d' { record2 = (g d') (record2 d') } 

Ma penso freccia notazione è più ordinato, e potrei anche avere [arr d d] e concatenare insieme in una sequenza. Inoltre se r o d sono Monade crea un codice più preciso. O se sono entrambi Monade, mi permetta di eseguire legature a strati senza dover usare un Monade Transformer. Nel caso di ST s x, mi consente di inserire lo stato s in modo ordinato.

Non sto cercando di risolvere un problema particolare. Sto solo cercando di trovare un metodo astratto per aggiornare una sintassi del record senza dover definire esplicitamente una sorta di "getter" e "setter".


Qui di seguito è stata risolta in senso comments-- Sidenote: Ho dovuto definire una funzione unarrow per la conversione di una funzione (->) arrow torna a una funzione. Altrimenti se ho someArrow b per una freccia arr b c non riesco a ottenere il valore c. Con la funzione unarrow posso scrivere unarrow someArrow b e funziona perfettamente. Mi sento come se dovessi fare qualcosa di sbagliato qui perché la mia definizione di unarrow è semplicemente unarrow g = g.

+0

La funzione 'unarrow' può essere rimosso se si dà' testRecord' una firma di tipo esplicito. C'è un problema con l'inferenza del tipo, non è possibile scoprire che 'testRecord' dovrebbe essere un tipo di funzione. – bmaderbacher

+0

Sarebbe bello se potessi chiarire un po 'quello che non vorresti ottenere. Vuoi applicare le funzioni (id in questo caso) ai campi di un record, come ho ipotizzato per la mia risposta? – bmaderbacher

+0

Grazie per il suggerimento su 'unarrow' @bmaderbacher. Stavo tormentando il mio cervello con il motivo per cui non potevo applicare la freccia in alcuni casi, definendo esplicitamente che è una funzione nella firma del tipo che ora sembra ovvia. – TheCriticalImperitive

risposta

7

L'astrazione che si sta cercando si chiama obiettivo e il pacchetto lens su Hackage è probabilmente l'implementazione più diffusa dell'idea attualmente in uso. Usando il pacchetto lens si può definire la recordArrow' come

{-# LANGUAGE RankNTypes #-} 

import Control.Arrow 
import Control.Lens 

recordArrow' :: Arrow arr => Lens' d r -> (r -> r) -> arr d d 
recordArrow' field f = arr $ field %~ f 

Il %~ è un operatore di aggiornamento che aggiorna un valori all'interno di un più grandi strutture di dati utilizzando una funzione tramite la lente data.

Ora il problema è che non si ottengono automaticamente obiettivi per i campi del record, ma è possibile definirli manualmente o generarli automaticamente utilizzando Template Haskell. Per esempio

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

data Testdata = Testdata { _record1::Int, _record2::Int, _record3::Int } 

makeLenses ''Testdata 

Si noti che le funzioni di accesso dei record originali sono preceduti da sottolineature in modo da poter utilizzare i nomi originali per le lenti.

testRecord :: Testdata -> Testdata 
testRecord = 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

non è necessaria la funzione unarrow. È meglio forzare un tipo generico a un tipo concreto dando semplicemente una firma di tipo.

Si noti che se si sta cercando una sintassi migliore per le operazioni di registrazione della catena e non si ha alcun altro uso per le frecce, si potrebbe preferire semplicemente utilizzare la monodia State con obiettivi. Per esempio:

import Control.Monad.State (execState) 

testRecord' :: Testdata -> Testdata 
testRecord' = execState $ do 
    record1 .= 3 
    record2 %= (+5) 
    record3 += 2 
+0

Grazie! Questo è esattamente quello che stavo cercando. Nella mia ricerca sono incappato in elenchi eterogenei e CTRex (http://www.haskell.org/haskellwiki/CTRex). Ma c'era un sacco di spese generali per quello che speravo sarebbe stato molto semplice. – TheCriticalImperitive

+0

Hai appena visto la tua domanda aggiornata e aggiunto un esempio di utilizzo degli obiettivi nella monade di stato. Questo potrebbe essere più vicino a quello che stai veramente cercando. – shang