He estado jugando con vectores y matrices donde el tamaño está codificado en su tipo, usando la nueva extensión DataKinds
. Básicamente, es la siguiente:Instancias y restricciones definidas recursivamente
data Nat = Zero | Succ Nat
data Vector :: Nat -> * -> * where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
Ahora queremos casos típicos como Functor
y Applicative
. Functor
es fácil:
instance Functor (Vector n) where
fmap f VNil = VNil
fmap f (VCons a v) = VCons (f a) (fmap f v)
Pero con la Applicative
ejemplo, hay un problema: No sabemos qué tipo de volver en el más puro. Sin embargo, podemos definir la instancia inductivamente en el tamaño del vector:
instance Applicative (Vector Zero) where
pure = const VNil
VNil <*> VNil = VNil
instance Applicative (Vector n) => Applicative (Vector (Succ n)) where
pure a = VCons a (pure a)
VCons f fv <*> VCons a v = VCons (f a) (fv <*> v)
Sin embargo, a pesar de que este caso se aplica para todos los vectores, el tipo de corrector no sabe esto, así que tenemos que llevar a la restricción Applicative
cada vez que usamos la instancia.
Ahora, si esto se aplica solo a la instancia Applicative
, no sería un problema, pero resulta que el truco de las declaraciones de instancia recursivas es esencial cuando se programa con tipos como estos. Por ejemplo, si definimos una matriz como un vector de vectores fila utilizando la biblioteca TypeCompose,
type Matrix nx ny a = (Vector nx :. Vector ny) a
tenemos que definir una clase de tipo y añadir declaraciones de instancias recursivas para implementar tanto la transpuesta y la multiplicación de matrices. Esto lleva a una gran proliferación de restricciones que tenemos que llevar cada vez que usamos el código, incluso aunque las instancias se apliquen a todos los vectores y matrices (haciendo que las restricciones sean inútiles).
¿Hay alguna manera de evitar tener que cumplir con todas estas limitaciones? ¿Sería posible extender el verificador de tipos para que pueda detectar tales construcciones inductivas?
Después de algunas lecturas y algo de experimentación, finalmente he entendido esta respuesta. Básicamente, la restricción HasNatty le permite realizar la recursión en el nivel de valor en lugar del nivel de tipo, eliminando la necesidad de restricciones adicionales. Eso ayuda mucho. Sin embargo, realmente estoy luchando para ver cómo implementar la multiplicación de matrices en términos de Traversable. ¿Podría dar algunos consejos? La mayoría de las implementaciones de multiplicación de matrices transponen primero una de las matrices. ¿Puedes obtener la matriz transpuesta con Traversable? – Almanildo
Sí.Si tiene las instancias anteriores para 'Applicative' y' Traversable', entonces 'transpose' es' traverse id'. Para ver esto, primero verifica los tipos. 'poligonal :: Vm aplicativo => (s -> vm t) -> Vector ns -> vm (Vector nt)' y ahora tomando 'vm = Vector m' y' s = Vector mt', obtenemos 'identificador transversal: : Vector n (Vector mt) -> Vector m (Vector nt) '. Operacionalmente, 'traverse id' lleva' VNil' a un vector de 'VNil', y hace vectorised-'VCons' a la fila superior y la transposición del resto, haciendo que la fila superior quede en la columna más a la izquierda. – pigworker
Correcto, pero eso todavía necesita la restricción HasNatty en el vector interno. Aún así, gracias, gracias. – Almanildo