I\'m trying to wrap my head around functional dependencies, but I am not getting anywhere on my own. In the paper \"Monad Transformers Step by Step\", the author gives thes
When Haskell is trying to resolve MonadError e m
it, by default, scours both the e
and m
parameters together looking for any pair which happens to have an instance. This is especially hard if we don't have an e
showing up anywhere in the type signature outside of the constraint itself
unitError :: MonadError e m => m ()
unitError = return ()
The functional dependency says that once we've resolved m
, there can be only one e
that works. That lets the above fragment compile as it reassures Haskell that there's enough information there for it to have a non-ambiguous type.
Without the functional dependency, Haskell would complain that unitError
is ambiguous because it could be valid for any type e
and we don't have any way to know what that type is—the information has somehow evaporated into thin air.
For MonadError
the functional dependency usually means that the monad itself is parameterized by the error type. For instance, here's an instance
instance MonadError e (Either e) where
thowError = Left
catchError (Left e) f = f e
catchError (Right a) _ = Right a
Where e ~ e
and m ~ Either e
and we see that m
does indeed uniquely identify a single e
which could be valid.
Functional dependencies are "nearly" equivalent to Type Families as well. Type Families are sometimes a little easier to digest. For instance, here's a MonadError
class, TypeFamilies style
{-# LANGUAGE TypeFamilies #-}
class MonadError m where
type Err m
throwError :: Err m -> m a
catchError :: m a -> (Err m -> m a) -> m a
instance MonadError (Either e) where
type Err (Either e) = e
throwError = Left
catchError (Left e) f = f e
catchError (Right a) _ = Right a
Here, Err
is a type function which takes a m
to its particular error type e
and the notion if there being exactly one e
equal to Err m
for any m
comes naturally from our understanding of functions.
When you have a multiparameter typeclass, by default, the type variables are considered independently. So when the type inferencer is trying to figure out which instance of
class Foo a b
to choose, it has to determine a
and b
independently, then go look check to see if the instance exists. With functional dependencies, we can cut this search down. When we do something like
class Foo a b | a -> b
We're saying "Look, if you determine what a
is, then there is a unique b
so that Foo a b
exists so don't bother trying to infer b
, just go look up the instance and typecheck that". This let's the type inferencer by much more effective and helps inference in a number of places.
This is particularly helpful with return type polymorphism, for example
class Foo a b c where
bar :: a -> b -> c
Now there's no way to infer
bar (bar "foo" 'c') 1
Because we have no way of determining c
. Even if we only wrote one instance for String
and Char
, we have to assume that someone might/will come along and add another instance later on. Without fundeps we'd have to actually specify the return type, which is annoying. However we could write
class Foo a b c | a b -> c where
bar :: a -> b -> c
And now it's easy to see that the return type of bar "foo" 'c'
is unique and thus inferable.
It means that the type system will always be able to determine the type (e
or r
) from the type m
.
Let's make our own example:
class KnowsA a b | b -> a where
known :: b -> a
We should always be able to determine a
from b
data Example1 = Example1 deriving (Show)
instance KnowsA Int Example1 where
known = const 1
The type system knows that for this instnace, whenever it has an Example1
, the type of it's a
will be Int
. How does it know? It doesn't allow us to have another instance with another type.
If we add
instance KnowsA [Char] Example1 where
known = const "one"
We get an error:
Functional dependencies conflict between instance declarations:
instance KnowsA Int Example1
instance KnowsA [Char] Example1
But we can add another instance with a different type for a
if we have a different type for b
data Example2 = Example2 deriving (Show)
instance KnowsA [Char] Example2 where
known = const "two"
main = do
print . known $ Example1
print . known $ Example2
This predictably outputs
1
"two"