2012-03-03 17 views
17

Lo que quiero decir es definir una instancia de una clase de tipo que se aplica en un ámbito local (let o where) en una función. Lo que es más importante, quiero que las funciones en este caso sean cierres, es decir, que se puedan cerrar las variables en el alcance léxico donde se define la instancia (lo que significa que la instancia posiblemente funcione de manera diferente la próxima vez que se llame a la función))¿Es posible tener una instancia de clase de tipo "local"?

Le puedo dar un caso de uso simplificado para esto. Supongamos que tengo una función que opera en un tipo basado en una clase de tipo. En este ejemplo, uso escuadrar, que funciona en cualquier tipo que las instancias Num (sí, la cuadratura es muy simple y se puede volver a implementar fácilmente, pero se presenta para algo que podría ser más complicado). Necesito poder utilizar la función existente tal como está (sin cambiarla ni volver a implementarla).

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

Ahora, supongamos que deseo utilizar esta operación en la aritmética modular, es decir, además, multiplicación, etc. mod algún número. Esto sería fácil de implementar para cualquier base de módulo fijo, pero quiero tener algo genérico que pueda reutilizar para diferentes bases de módulo. Quiero ser capaz de definir algo como esto:

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 

El punto de esto es que necesito para utilizar la función de arriba (square) que requiere su argumento de que es un tipo que es una instancia de un determinado tipo de clase. Defino un nuevo tipo y lo convierto en una instancia de Num; sin embargo, para realizar aritmética de módulo correctamente, depende de la base de módulo n, que, debido al diseño genérico de esta función, puede cambiar de llamada a llamada. Deseo definir las funciones de instancia como una especie de "devoluciones de llamada" (si se quiere) para la función square para personalizar cómo realiza las operaciones esta vez (y solo esta vez).

Una solución podría ser integrar las "variables de cierre" directamente en el tipo de datos (es decir, ModN (x, n) para representar el número y la base a la que pertenece), y las operaciones pueden extraer esta información de los argumentos. Sin embargo, esto tiene varios problemas: 1) Para funciones de múltiples argumentos (por ejemplo, (*)), debería verificar en tiempo de ejecución que esta información coincida, lo cual es feo; y 2) la instancia puede contener "valores" de 0 argumentos que me gustaría depender de las variables de cierre, pero que, como no contienen argumentos, no pueden extraerlos de los argumentos.

+2

No, no puede haber instancias locales. Para la aritmética modular, Oleg Kiselyov y CC Shan han resuelto el problema en su documento "Configuraciones implícitas" - http://www.cs.rutgers.edu/~ccshan/prepose/prepose.pdf. Personalmente tiendo a evitar esto y simplemente hago un nuevo tipo para la base modular, es decir, Z7, Z12. Conal Elliott tiene otro patrón para seleccionar clases de tipos alternativas, ver CxMonoid en el documento "Computación Aplicada a Datos Aplicativos" - http://conal.net/papers/data-driven/paper.pdf –

+1

De hecho, el paquete de reflexión es esencialmente el versión en paquete del papel Oleg. – ehird

+0

Con respecto al estado actual de Haskell: no. Las instancias irán a todas partes y a cualquier lugar al que puedan llegar. En cuanto a posibles ajustes a Haskell en el futuro: tal vez. En cuanto al uso de su propio mecanismo en lugar de clases de tipos: sí. –

Respuesta

11

La extensión propuesta tiene el mismo problema demostrado en this previous answer de la mía; puede usar las instancias locales para crear dos Map s con el mismo tipo de clave pero diferentes instancias Ord, lo que hace que todas las invariantes se caigan.

Sin embargo, el paquete de reflection le permite definir un tipo tal ModN: se define una sola instancia con una restricción Reifies, y activar el ejemplo para un determinado n con reify. (Creo que implicit parameters también lo haría posible, pero esa extensión raramente se usa.)

Cuestiones relacionadas