Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typeclass constraints with higher kinded types

Tags:

haskell

I'm trying to write an Eq instance for an EitherT newtype given by:

newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }

I assumed the following Eq instance would work:

instance (Eq e, Eq a, Eq m) => Eq (EitherT e m a) where
  a == b = (runEitherT a) == (runEitherT b)

However, I'm seeing an error:

Expected kind '* -> *', but 'm' has kind '*'

What I'm reading from that error is that my typeclass constraint ( ... Eq m) => ... is confusing the compiler into thinking that I believe m to be of kind *, when my newtype declaration for EitherT expects it to be of kind * -> *.

I'm wondering what I need to do, to declare that I want an Eq instance for some higher kinded type m to implement Eq for my EitherT newtype.

Edit: As pointed out by @AlexisKing, I can get this to work with:

{-# LANGUAGE UndecideableInstances #-}
instance (Eq (m (Either e a))) => Eq (EitherT e m a) where
  a == b = (runEitherT a) == (runEitherT b)

However, it seems strange to me to that a language extension is required to write this Eq instance. Is there no other way to express such a typeclass constraint in vanilla Haskell? If not, why?

like image 782
Wilduck Avatar asked Jan 04 '23 02:01

Wilduck


1 Answers

You're looking for Eq1 which is in Data.Functor.Classes since base 4.9.0.0. Before that it was in one of the -extras packages or transformers? (it's in transformers now since 0.4.0.0)

Eq1 f says that you can compare fs as long as you have a way to compare their contents

class Eq1 f where
    liftEq :: (a -> b -> Bool) -> f a -> f b -> Bool

In your case you'd use it like

instance (Eq e, Eq1 m) => Eq1 (EitherT e m) where
   liftEq f a b = liftEq (liftEq f) (runEitherT a) (runEitherT b)

The liftEq f is to use the existing Eq1 instance for Either.

And can define an Eq instance as

instance (Eq e, Eq a, Eq1 m) => Eq (EitherT e m a) where
   (==) = liftEq (==)

The old Eq1 was

class Eq1 f where
    eq1 :: (Eq a) => f a -> f a -> Bool

In your case you'd use it like

instance (Eq e, Eq1 m) => Eq1 (EitherT e m) where
   eq1 a b = eq1 (runEitherT a) (runEitherT b)

instance (Eq e, Eq a, Eq1 m) => Eq1 (EitherT e m) where
   a == b = eq1 (runEitherT a) (runEitherT b)
like image 129
Cirdec Avatar answered Jan 13 '23 23:01

Cirdec