Ambiguous type of functional dependency
In the haskell functonal dependency wiki :
Given these definitions:
data Vector = Vector Int Int deriving (Eq, Show)
data Matrix = Matrix Vector Vector deriving (Eq, Show)
instance Num Vector where
Vector a1 b1 + Vector a2 b2 = Vector (a1+a2) (b1+b2)
Vector a1 b1 - Vector a2 b2 = Vector (a1-a2) (b1-b2)
{- ... and so on ... -}
instance Num Matrix where
Matrix a1 b1 + Matrix a2 b2 = Matrix (a1+a2) (b1+b2)
Matrix a1 b1 - Matrix a2 b2 = Matrix (a1-a2) (b1-b2)
{- ... and so on ... -}
class Mult a b c where
(*) :: a -> b -> c
instance Mult Matrix Matrix Matrix where
{- ... -}
instance Mult Matrix Vector Vector where
{- ... -}
I can't figure out why there is an ambiguous type for:
m1, m2, m3 :: Matrix
(m1 * m2) * m3 -- type error; type of (m1*m2) is ambiguous
Obviously, when m1 and m2 are matrices, the only possible return type is matrix, that is, application instance Mult Matrix Matrix Matrix
.
source to share
The problem is in the class declaration like
class Mult a b c where
(*) :: a -> b -> c
Applying (*)
on two arguments, you have no way to determine the type of the result. Let's say you have two instances:
instance Mult Int Int Int where ...
instance Mult Int Int Integer where ...
Then 2 * 4
can have a type Int
as well Integer
.
Now you can argue that you only have one instance, so the compiler shouldn't complain. But classes like Haskell live in the open world. You can always add more instances and it shouldn't interrupt your code elsewhere. So even if you only had one instance, someone from the other library could add another one. And you will have two libraries, each working, but not working together. It would be perfectly obvious. See Open World Living in the Real World of Haskell.
Thus, as a general rule, the return types of a function in a type class should be inferred from its arguments. This is exactly what functional dependencies are for. If you declare
class Mult a b c | a b -> c where
then the compiler can always tell what the return type is (*)
.
source to share
Because you forgot all other possibilities, for example
instance Mult Matrix Matrix Vector where
instance Mult Vector Matrix Vector where
instance Mult Float Matrix Float where
instance Mult Matrix Matrix Float where -- etc.
a :: Vector
a = (m1 >< m2:: Vector) >< m3
b :: Float
b = (m1 >< m2:: Float) >< m3
Adding functional dependency to class definition:
instance Mult a b c | a b -> c
would mean that
instance Mult Matrix Matrix Matrix
solves the question for two matrices and that
instance Mult Matrix Matrix Vector
instance Mult Matrix Matrix Float
are excluded because they give a different way of looking at the "product of a Matrix
and a Matrix
". Thus, the state of affairs you find intuitive is the one you get with functional dependency.
If the functional dependence was formulated as follows:
instance Mult a b c | b -> a c
this will also resolve two instances that you specify,
instance Mult Matrix Matrix Matrix where
instance Mult Matrix Vector Vector where
but exclude others that I imagined, and they all have Matrix
in position b
(as necessary, since in an ambiguous example the product of matrices m3
was explained as Matrix
and not as a result.
source to share