Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring Eq typeclass

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?

like image 220
utdemir Avatar asked Mar 13 '15 09:03

utdemir


1 Answers

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
...
like image 146
chi Avatar answered Oct 16 '22 10:10

chi