I'm writing a CRUD like application and have a lot of lookups by primary key(primary keys can have different types). So I defined following typeclass:
{-# LANGUAGE MultiParamTypeClasses #-}
class Eq b => HasPK a b where
getPK :: a -> b
Now I can write:
import Data.Maybe
lookupPK :: HasPK a b => b -> [a] -> Maybe a
lookupPK s = listToMaybe . filter ((== s) . getPK)
Now, when I want to compare two things with PK, I just want to compare their PK's. So, I'm trying to define this:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
instance (HasPK a b) => Eq a where
(==) = (==) `on` getPK
But now it gives me:
src/Utils.hs:61:10: Could not deduce (HasPK a b0) …
arising from the ambiguity check for an instance declaration
from the context (HasPK a b)
bound by an instance declaration: HasPK a b => Eq a
at /home/utdemir/workspace/.../Utils.hs:61:10-28
The type variable ‘b0’ is ambiguous
In the ambiguity check for: forall a b. HasPK a b => Eq a
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the instance declaration for ‘Eq a’
Compilation failed.
Can anyone explain this error to me? Am I on the right track, or is there a safer way to achieve what I want?
You need a functional dependency here: use
class Eq b => HasPK a b | a -> b where
getPK :: a -> b
and enable whatever extension GHC points to.
The issue lies in the possibility of having multiple PKs for the same type, as in
instance HasPK MyType Int where ...
instance HasPK MyType String where ...
Since the above double instance could be added later, code like (==) `on` getPK
is potentially ambiguous. Indeed, when the instances above are added, it does not specify whether to use the Int
PK or the String
one.
However, an instance like
instance (HasPK a b) => Eq a where
...
is likely to cause problems, since it overlaps with any other Eq
instance. Be careful here. I would instead write
equalPK :: HasPK a b => a -> a -> Bool
equalPK = (==) `on` getPK
and then provide explicit instances to all the relevant types as this:
instance Eq MyType1 where (==) = equalPK
instance Eq MyType2 where (==) = equalPK
...
As another alternative, you can use a type family such as
class HasPK a where
type PK a
getPK :: a -> PK a
equalPK :: Eq (PK a) => a -> a -> Bool
equalPK = (==) `on` getPK
instance Eq MyType1 where (==) = equalPK
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With