I made a version of the Convertible class like so:
class Convertible a b where
convert :: a -> b
instance (Convertible a b, Functor f) => Convertible (f a) (f b) where
convert = fmap convert
However, I found it annoying that I would have to make a new instance if I ever wanted to string two conversions together. So I tried adding this:
instance (Convertible a b, Convertible b c) => Convertible a c where
convert = convert . convert
The compiler complained with this:
Variable ‘b’ occurs more often than in the instance head
in the constraint: Convertible a b
(Use UndecidableInstances to permit this)
In the instance declaration for ‘Convertible a c’
Variable ‘b’ occurs more often than in the instance head
in the constraint: Convertible b c
(Use UndecidableInstances to permit this)
In the instance declaration for ‘Convertible a c’
At this point I understand why the compiler is complaining at me, and I'd really rather not turn on UndecidableInstances. The way I currently have my instances set up, there's only one instance of Convertible a b for each a. I hoped that adding a functional dependency a -> b would alleviate this, but now the compiler is complaining about the functor instance:
Illegal instance declaration for ‘Convertible (f a) (f b)’
The coverage condition fails in class ‘Convertible’
for functional dependency: ‘a -> b’
Reason: lhs type ‘f a’ does not determine rhs type ‘f b’
Using UndecidableInstances might help
In the instance declaration for ‘Convertible (f a) (f b)’
Any thoughts about how to get this working? Or perhaps a better design if necessary? I'm thinking I may just have to settle for being explicit about which path of conversions to take.