sintassi record di Haskell e classi di tipo
Domanda
Supponiamo che ho due tipi di dati foo e bar. Foo ha campi x ed y. Bar ha campi x e z. Voglio essere in grado di scrivere una funzione che prende sia un Foo o un bar come parametro, estrae il valore x, esegue qualche calcolo su di esso, e quindi restituisce una nuova Foo o bar con il valore x impostato di conseguenza.
Ecco un approccio:
class HasX a where
getX :: a -> Int
setX :: a -> Int -> a
data Foo = Foo Int Int deriving Show
instance HasX Foo where
getX (Foo x _) = x
setX (Foo _ y) val = Foo val y
getY (Foo _ z) = z
setY (Foo x _) val = Foo x val
data Bar = Bar Int Int deriving Show
instance HasX Bar where
getX (Bar x _) = x
setX (Bar _ z) val = Bar val z
getZ (Bar _ z) = z
setZ (Bar x _) val = Bar x val
modifyX :: (HasX a) => a -> a
modifyX hasX = setX hasX $ getX hasX + 5
Il problema è che tutti quei getter e setter sono dolorosi di scrivere, soprattutto se sostituisco Foo e Bar con i tipi di dati del mondo reale che hanno un sacco di campi.
sintassi record di Haskell offre un modo molto più bello di definire questi record. Ma, se provo a definire i dischi come questo
data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show
Prendo un errore che dice che x è definito più volte. E, non sto vedendo un modo per rendere questi parte di una classe di tipo in modo che io possa passare al modifyX.
C'è un bel modo pulito per risolvere questo problema, o sto bloccato con la definizione miei getter e setter? In altre parole, c'è un modo di collegare le funzioni create dalla sintassi verbale con le classi di tipo (sia i getter e setter)?
Modifica
Ecco il vero problema che sto cercando di risolvere. Sto scrivendo una serie di programmi correlati che tutti utilizzano System.Console.GetOpt per analizzare le loro opzioni della riga di comando. Ci saranno un sacco di opzioni della riga di comando che sono comuni a tutti questi programmi, ma alcuni dei programmi possono avere opzioni extra. Mi piacerebbe ogni programma per essere in grado di definire un record che contiene tutti i suoi valori di opzione. Sono quindi iniziare con un valore record di default che viene poi trasformato attraverso una monade StateT e getopt di ottenere un record finale riflette gli argomenti della riga di comando. Per un singolo programma, questo approccio funziona davvero bene, ma sto cercando di trovare un modo per riutilizzare il codice in tutti i programmi.
Soluzione
Si vuole estensibile registra che, ho capito, è uno dei più parlato di argomenti in Haskell. Sembra che non ci sia attualmente molto consenso su come implementarlo.
Nel tuo caso sembra che forse, invece di un record ordinaria si potrebbe usare una lista eterogenea, come quelle attuate in HList .
Poi di nuovo, sembra avere solo due livelli qui: comune e programma. Così forse si dovrebbe solo definire un tipo di record comune per le opzioni comuni e uno specifico programma tipo di record per ogni programma, e utilizzare StateT su una tupla di quei tipi. Per la roba comune è possibile aggiungere degli alias che compongono fst
con le funzioni di accesso comuni in modo che sia invisibile ai chiamanti.
Altri suggerimenti
Si potrebbe utilizzare il codice come
data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show)
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show)
instance HasX Foo where
getX = fooX
setX r x' = r { fooX = x' }
instance HasX Bar where
getX = barX
setX r x' = r { barX = x' }
Che cosa stai modellando nel codice? Se sapessimo di più sul problema, potremmo suggerire qualcosa di meno imbarazzante di questa progettazione orientata agli oggetti infilata in un linguaggio funzionale.
Mi sembra come un lavoro per i farmaci generici. Se si potesse etichettare il vostro Int con diversi newtypes, allora si sarebbe in grado di scrivere (con UniPlate, modulo PlateData):
data Foo = Foo Something Another deriving (Data,Typeable)
data Bar = Bar Another Thing deriving (Data, Typerable)
data Opts = F Foo | B Bar
newtype Something = S Int
newtype Another = A Int
newtype Thing = T Int
getAnothers opts = [ x | A x <- universeBi opts ]
Ciò estrarre tutti i Un altro di da qualsiasi punto all'interno della Opz.
La modifica è possibile pure.
Se si effettua le istanze tipi di pieghevole si ottiene una funzione toList che è possibile utilizzare come base del vostro accesso.
Se pieghevole non lo fa per niente, allora forse il giusto approccio è quello di definire l'interfaccia che si desidera come una classe tipo e capire un buon modo per autogenerare i valori derivati.
Forse derivando dal fare
deriving(Data)
è possibile utilizzare combinatori gmap basare l'accesso off.