Skip to content

Instantly share code, notes, and snippets.

@Icelandjack
Created May 27, 2017 16:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Icelandjack/e42495341f6029aad8c7e4e4a12c34ce to your computer and use it in GitHub Desktop.
Save Icelandjack/e42495341f6029aad8c7e4e4a12c34ce to your computer and use it in GitHub Desktop.
Define multiple instances simultaneously

Idea from https://gist.github.com/Icelandjack/e1ddefb0d5a79617a81ee98c49fbbdc4

Works great for concrete types

data A = MkA

instance (Eq & Ord & Show & Arbitrary) A where
  (==) :: A -> A -> Bool
  MkA == MkA = True
  
  compare :: A -> A -> Ordering
  MkA `compare` MkA = EQ
  
  show :: A -> String
  show MkA = "MkA"
  
  arbitrary :: Gen A
  arbitrary = pure MkA
@Icelandjack
Copy link
Author

Icelandjack commented May 27, 2017

Not so nice for type constructors

newtype B a = MkB a

type EOSA = Eq & Ord & Show & Arbitrary

instance EOSA b => EOSA (B b) where
  (==) :: B b -> B b -> Bool
  MkB a == MkB b = a `compare` b
  
  compare :: B b -> B b -> Ordering
  MkB a `compare` MkB b = a `compare` b
  
  show :: B b -> String
  show (MkB b) = "MkB " ++ show b
  
  arbitrary :: Gen (B b)
  arbitrary = MkB <$> arbitrary

Now all of a sudden (==) @(B _) :: EOSA b => B b -> B b -> Bool which is ridiculous. We should be able to tailor the context to each method or group of methods.

@Icelandjack
Copy link
Author

It could be magic... (inferred from methods, and then crushed together) or determined from explicit instance sigs (and then crushed)

instance EOSA (B b) where
  (==) :: Eq b => B b -> B b -> Bool
  MkB a == MkB b = a == b

  compare :: B b -> B b -> Ordering
  MkB a `compare` MkB b = a `compare` b

  show :: Show b => B b -> String
  show (MkB b) = "MkB " ++ show b 
...

and the methods have the same constraints as provided, and the instances become

instance Eq        b => Eq        (B b)
instance Ord       b => Ord       (B b)
instance Show      b => Show      (B b)
instance Arbitrary b => Arbitrary (B b)

@Icelandjack
Copy link
Author

So if inferred from method's required constraints, then we have

instance EOSA (B b) where
  a == b = compare a b == EQ
  MkB a `compare` MkB b = compare a b

resulting in

instance Ord a => Eq  (B b)
instance Ord a => Ord (B b)

@Icelandjack
Copy link
Author

We merge the contexts of methods from the same type class just as usual

class  EQ a where  eq :: a -> a -> Bool
class NEQ a where neq :: a -> a -> Bool

newtype C a = C a
  deriving (EQ, NEQ)

instance (EQ a, NEQ a) => Eq (C a) where
  (==) = eq
  (/=) = neq

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment