In Haskell, if you have a "family" of types (say, N-by-N-element matrices, for some values of N), and a parallel family of "related" types (say, N-element vectors, for the same values of N), and an operation that requires one specific type from each family (say, multiplying an N-by-N-element matrix and an N-element column vector), is it then possible to declare a type class for that operation?
For this specific example, I imagine it would look something like this:
class MatrixNxN m where
--| Multiplication of two N-by-N-element matrices
mmul :: Num a => m a -> m a -> m a
--| Multiplication of an N-by-N-element matrix and an N-element column vector
vmul :: Num a => m a -> v a -> v a
I don't know how to constrain the type v
, however. Is it possible to do something like this?
Please note that I welcome both answers to the general question of declaring a type class of multiple, related types, as well as answers to the specific question of declaring a type class for matrix-vector multiplication. In my specific case, there is only a small, known set of values of N (2, 3, and 4), but I'm generally interested in understanding what is possible to encode in Haskell's type system.
EDIT: I implemented this using MultiParamTypeClasses
and FunctionalDependencies
as suggested by Gabriel Gonzalez and MFlamer below. This is what the relevant bits of my implementation ended up looking like:
class MatrixVectorMultiplication m v | m -> v, v -> m where
vmul :: Num a => m a -> v a -> v a
data Matrix3x3 a = ...
data Vector3 a = ...
instance MatrixVectorMultiplication Matrix3x3 Vector3 where
vmul = ...
This is the type signature of vmul
, on its own and partially applied:
vmul :: (Num a, MatrixVectorMultiplication m v) => m a -> v a -> v a
(`vmul` v) :: Matrix3x3 Integer -> Vector3 Integer
(m `vmul`) :: Vector3 Integer -> Vector3 Integer
I find this all very elegant. Thanks for the answers! :)
This is a very tiny variation on MFlamer
's answer, which also makes m
dependent on v
:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class MatrixNxN m v | m -> v, v -> m where
mmul :: Num a => m a -> m a -> m a
vmul :: Num a => m a -> v a -> v a
That way, if you do:
(`vmul` someVector)
... then the compiler can select the correct instance on the basis of someVector
's type alone.
The type family solution won't work for the same reason, mainly because if you declare the v
type constructor to be a type function of the m
type constructor, that type function is not necessarily 1-to-1, so the compiler wouldn't be able to infer m
from v
. This is why people say that functional dependencies are more powerful than type families.