2012-03-03 19 views
17

Quello che intendo è definire un'istanza di una classe di tipi che si applica in un ambito locale (let o where) in una funzione. Ancora più importante, voglio che le funzioni in questo caso siano le chiusure, ovvero essere in grado di chiudere le variabili nello scope lessicale in cui è definita l'istanza (il che significa che l'istanza potrebbe funzionare diversamente la prossima volta che viene chiamata la funzione in cui si trova)).È possibile avere un'istanza di classe tipica "locale"?

Posso darvi un caso d'uso semplificato per questo. Supponiamo che abbia una funzione che opera su un tipo basato su una classe di tipo. In questo esempio io uso quadratura, che opera su qualsiasi tipo che le istanze Num (sì, la quadratura è molto semplice e può essere facilmente re-implementata, ma è in piedi per qualcosa che potrebbe essere più complicato). Devo essere in grado di utilizzare la funzione esistente così com'è (senza cambiarla o ri-implementarla).

square :: Num a => a -> a 
square x = x * x 

Ora, supponiamo che io desidero usare questa operazione aritmetica modulare, cioè addizione, moltiplicazione, ecc mod qualche numero. Questo sarebbe facile da implementare per qualsiasi modulo base fisso, ma voglio avere qualcosa di generico che possa essere riutilizzato per diverse basi modulo. Voglio essere in grado di definire qualcosa di simile:

newtype ModN = ModN Integer deriving (Eq, Show) 

-- computes (x * x) mod n 
squareModN :: 
squareModN x n = 
    let instance Num ModN where 
    ModN x * ModN y = ModN ((x * y) `mod` n) -- modular multiplication 
    _ + _ = undefined   -- the rest are unimplemented for simplicity 
    negate _ = undefined 
    abs _ = undefined 
    signum _ = undefined 
    fromInteger _ = undefined 
    in let ModN y = square (ModN x) 
    in y 

Il punto di questo è che ho bisogno di usare la funzione dall'alto (square) che richiede il suo argomento di essere un tipo che è un'istanza di una certa tipo di classe. Definisco un nuovo tipo e ne faccio un'istanza di Num; tuttavia, per eseguire correttamente l'aritmetica modulo, dipende dal modulo base n, che, a causa del design generico di questa funzione, potrebbe cambiare da chiamata a chiamata. Desidero definire le funzioni di istanza come tipi di "richiamate" una tantum (se lo farete) per la funzione square per personalizzare il modo in cui esegue le operazioni questa volta (e solo questa volta).

Una soluzione potrebbe essere quella di integrare le "variabili di chiusura" direttamente nel tipo di dati stesso (ad esempio ModN (x, n) per rappresentare il numero e la base a cui appartiene) e le operazioni possono semplicemente estrarre queste informazioni dagli argomenti. Tuttavia, questo ha diversi problemi: 1) Per le funzioni a più argomenti (ad esempio (*)), è necessario verificare in fase di esecuzione che queste informazioni corrispondano, il che è brutto; e 2) l'istanza potrebbe contenere "valori" di argomento 0 che potrei voler dipendere dalle variabili di chiusura, ma che, poiché non contengono argomenti, non possono estrarli dagli argomenti.

+2

No, non è possibile avere istanze locali. Per l'aritmetica modulare Oleg Kiselyov e CC Shan hanno risolto il problema nel loro documento "Configurazioni implicite" - http://www.cs.rutgers.edu/~ccshan/prepose/prepose.pdf. Personalmente tendo ad evitare questo e semplicemente creare un nuovo tipo per la base modulare - cioè Z7, Z12. Conal Elliott ha un altro modello per selezionare classi di tipi alternativi, vedi CxMonoid nel documento "Calcolo basato sui dati applicativi" - http://conal.net/papers/data-driven/paper.pdf –

+1

In effetti, il pacchetto di riflessione è essenzialmente il versione confezionata della carta Oleg. – ehird

+0

Per quanto riguarda lo stato attuale di Haskell: no. Le istanze andranno ovunque e ovunque possano raggiungere. Per quanto riguarda i possibili aggiustamenti a Haskell in futuro: forse. Riguardo l'uso del tuo meccanismo al posto delle classificazioni: sì. –

risposta

11

L'estensione proposta ha lo stesso problema dimostrato nel mio this previous answer; è possibile utilizzare le istanze locali per creare due Map s con lo stesso tipo di chiave ma diverse istanze Ord, causando l'arresto anomalo di tutti gli invarianti.

Tuttavia, il pacchetto reflection consente di definire un tale tipo ModN: si definisce una singola istanza con un vincolo Reifies, e attivare l'istanza di un particolare n con reify. (Credo che lo implicit parameters renderebbe ciò possibile, ma quell'estensione è usata raramente.)

Problemi correlati