I have a class as follows:
class Token a where
symbol :: a -> String
I also want all instances of Token
to have a function convert
which returns a parametrised type. The conversion alone works fine:
class Token a b where
convert :: a -> b
data Egal = One | Two
instance Token Egal Int where
convert One = 111
convert Two = 222
main = print $ show (convert One :: Int)
But when I try to use both symbol
and convert
I get errors about ambiguity. This is my code:
class Token a b where
convert :: a -> b
symbol :: a -> String
data Egal = One | Two
instance Token Egal Int where
convert One = 111
convert Two = 222
symbol One = "one"
symbol Two = "two"
main = print $ show (convert One :: Int)
What am I doing wrong?
EDIT: Reading my own question I started wondering: Should these be two distinct classes and my data Egal
show instanciate both?
As you have defined things here, you can have instances with the same a
but conflicting b
s. Like this:
instance Token Char Char where
convert = id
symbol c = [c]
instance Token Char Bool where
convert = (>'m')
symbol c = [c, c, c, c]
Now, should symbol 'x'
be "x"
or "xxxx"
? Both are possible depending which of the above instances gets chosen; it is ambiguous which instance should be used for symbol
, and therefore which answer you should get. There are various ways to fix this. One is to simply allow the ambiguity, and give yourself the ability to specify which instance to use at call sites. You can turn on the AllowAmbiguousTypes
and TypeApplications
extensions; then:
> symbol @_ @Char 'x' -- or, more explicitly, symbol @Char @Char 'x'
"x"
> symbol @_ @Bool 'x' -- explicitly, symbol @Char @Bool 'x'
"xxxx"
But in many cases, you really want the compiler to check that you haven't made multiple instances with conflicting a
s. Then you can use either the FunctionalDependencies
extension:
class Token a b | a -> b where
convert :: a -> b
symbol :: a -> String
instance Token Char Char where {- ... -}
or the TypeFamilies
extension:
class Token a where
type Conversion a
convert :: a -> Conversion a
symbol :: a -> String
instance Token Char where
type Conversion Char = Char
{- ... -}
They have more or less the same effect in most cases: conflicting instances are flagged, and there is no ambiguity left.