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.
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 –
In effetti, il pacchetto di riflessione è essenzialmente il versione confezionata della carta Oleg. – ehird
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ì. –