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.

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top