2011-10-22 9 views
5

Per farla semplice, userò questa classe esempio inventato (il punto è che abbiamo alcuni dati costosi derivati ​​da metodi):Memoizzazione e typeclasses

class HasNumber a where 
    getNumber :: a -> Integer 
    getFactors :: a -> [Integer] 
    getFactors a = factor . getNumber 

Naturalmente, possiamo fare implementazioni memoizing di questa classe come ad esempio:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Integer, 
    fooFactors :: [Integer] 
} 

foo :: String -> Integer -> Foo 
foo a n = Foo a n (factor n) 

instance HasNumber Foo where 
    getNumber = fooNumber 
    getFactors = fooFactors 

Ma sembra un po 'brutto per essere richiesto di aggiungere manualmente un campo di 'fattori' a qualsiasi record che sarà un esempio HasNumber. Successivo idea:

data WithFactorMemo a = WithFactorMemo { 
    unWfm :: a, 
    wfmFactors :: [Integer] 
} 

withFactorMemo :: HasNumber a => a -> WithFactorMemo a 
withFactorMemo a = WithFactorMemo a (getFactors a) 

instance HasNumber a => HasNumber (WithFactorMemo a) where 
    getNumber = getNumber . unWfm 
    getFactors = wfmFactors 

Ciò richiederà un sacco di testo standard per il sollevamento tutte le altre operazioni dell'originale a in WithFactorMemo a, però.

Ci sono soluzioni eleganti?

+0

Un'altra soluzione che ho appena pensato sarebbe quello di rendere la funzione * fattore * memoizing, anche se questo sarebbe meno pratico se il risultato di 'getNumber' fosse una struttura di dati più grande, e (per quanto ne so) le voci non avrebbero mai ottenere garbage collection (in contrasto con le due soluzioni nella mia domanda). – FunctorSalad

risposta

7

Ecco la soluzione: perdere il typeclass. Ho parlato di questo here e here. Qualsiasi classe di caratteri TC a per la quale ognuno dei suoi membri prende un singolo a come argomento è isomorfo a un tipo di dati. Ciò significa che ogni istanza della classe HasNumber può essere rappresentato in questo tipo di dati:

data Number = Number { 
    getNumber' :: Integer, 
    getFactors' :: [Integer] 
} 

Vale a dire, per questa trasformazione:

toNumber :: (HasNumber a) => a -> Number 
toNumber x = Number (getNumber x) (getFactors x) 

E Number è ovviamente un caso di HasNumber pure.

instance HasNumber Number where 
    getNumber = getNumber' 
    getFactors = getFactors' 

Questo isomorfismo ci dimostra che questa classe è un tipo di dati in incognito, e dovrebbe morire. Basta usare Number invece. Potrebbe essere inizialmente non ovvio come farlo, ma con una piccola esperienza dovrebbe venire rapidamente. . Ad esempio, il tipo di Foo diventa:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Number 
} 

tuo Memoizzazione verrà poi venire gratuitamente, perché i fattori sono memorizzati nella struttura dati Number.

+0

In realtà, questo è quello che ho deciso di provare anche poco prima di pubblicare :) Sono d'accordo nel mettere le operazioni in un singolo tipo ('Numero' qui), ma forse è comunque una buona idea avere una' classe HasNumber a dove numberDict :: a -> Number' insieme ai wrapper 'getNumber = getNumber '. numberDict' e così via. Ma si deve effettivamente memorizzare il 'Numero' nei record che devono essere' HasNumber', piuttosto che creare un 'Number' da un numero intero nell'implementazione' numberDict' (che, naturalmente, ci lascerebbe di nuovo senza memoization) . – FunctorSalad

+0

Mi raccomando caldamente contro la classe di caratteri in casi come questo, sarà solo a modo tuo. Basta modellarlo concretamente, il toolbox FP è più adatto a quel tipo di programmazione, il linguaggio è più efficace nell'assorbire sui tipi di dati che nelle classifiche, e non ti lascia ingannare te stesso che stai facendo la modellazione OO (che tu sei non - e se stai pensando in questo modo, anche senza rendertene conto, il linguaggio finirà per limitarti lungo la strada). – luqui