2012-04-02 20 views
7

L'idea di base è che ho una gamma di funzioni che funzionano su qualsiasi tipo da una particolare classe, ma in fase di esecuzione il programma dovrebbe leggere un file di configurazione ed estrarre un elemento di uno dei tipi nella classe.Implementazione trasparente di una particolare forma di digitazione dinamica

Ad esempio, ho una classe 'Coefficiente', varie istanze di esso e funzioni di vari tipi che sono polimorfiche su tipi di quella classe; in fase di esecuzione, è necessario determinare un tipo particolare di tale classe e passarlo in giro.


Non sono sicuro di come affrontare correttamente questo; Ho provato a fare fino tipi 'composti', facendo qualcosa di simile:

data CompoundCoeff = CompoundInt Int | CompoundDouble Double | ... 

dove int, double, ... sono le istanze del 'coefficiente' di classe.
Tuttavia, ha iniziato a diventare un grande sforzo per adattare tutte le funzioni coinvolte nel codice per lavorare con questi tipi di composti (e non è nemmeno una bella soluzione, davvero). Sarebbe OK se tutte le funzioni avessero lo stesso tipo facile, ad es.

Coefficient a => a -> (stuff not involving a anymore) 

ma sfortunatamente non è il caso.

Un altro problema che ho incontrato, è che sto usando le famiglie tipo, e avere qualcosa di simile

class (Monoid (ColourData c), Coordinate (InputData c)) => ColourScheme c where 
    type ColourData c :: * 
    type InputData c :: * 
    colouriseData  :: c -> (ColourData c) -> AlphaColour Double 
    processInput  :: c -> InputData c -> ColourData c 

Questo non passare attraverso in modo pulito se devo utilizzare una sorta di composto ColourData tipo di dati, come il precedente; in particolare, non posso più garantire che il flusso di dati fornisca un tipo coerente (e non solo "sottotipi" diversi di un tipo composto) e (tra le altre cose) debba creare un'istanza Monoid falsa se io componga un composto tipo ColourData.

Ho anche esaminato Data.Dynamic, ma di nuovo non riesco a vedere come avrebbe affrontato correttamente i problemi; gli stessi identici problemi sembrano apparire (beh, anche un po 'peggio, dato che c'è solo un tipo' generico 'dinamico come lo capisco).


Domanda: Come posso implementare tipi di dati dinamici subordinati alle classi particolari, senza dover riscrivere tutte le funzioni che coinvolgono questi tipi di dati? Sarebbe meglio se non dovessi sacrificare alcun tipo di sicurezza, ma non sono troppo ottimista.
Si suppone che il programma legga un file di configurazione in fase di esecuzione e che tutte le funzioni necessarie, polimorfiche rispetto alla classe pertinente, vengano applicate.

risposta

7

Il modo tradizionale per fornire un oggetto che garantisce che si tratta di un esempio di typeclass Foo, ma non fornisce alcuna garanzia aggiuntivi, è in questo modo:

{-# LANGUAGE ExistentialTypes #-} 
data SomeFoo = forall a . Foo a => SomeFoo a 

instance Foo SomeFoo where 
    -- all operations just unwrap the SomeFoo straightforwardly 

o con GADTs, che potrebbero essere più leggibile. ..

data SomeFoo where 
    SomeFoo :: Foo a => a -> SomeFoo 
6

Una proposta potrebbe essere quella di scrivere una sola funzione di primo livello che fa tutto gli ultimi ritocchi una volta che hai scelto un tipo:

topLevel :: SomeTypeClass a => a -> IO() 

Il programma può quindi essere scritto qualcosa di simile a questo:

main = do 
    config <- readConfig 
    case config of 
     UseDouble n -> topLevel n 
     UseSymbolic x -> topLevel x 
     UseWidgetFrobnosticator wf -> topLevel wf 
Problemi correlati