Определение экземпляра Eq для GADT Haskell

У меня есть GADT, что очень похоже на это:

data In a where
  M :: MVar a -> In a
  T :: TVar a -> In a
  F :: (a -> b) -> In a -> In b

Он обертывает различные входные примитивы, но последний конструктор также допускает экземпляр Functor:

instance Functor In where
  fmap f (F g v) = F (f . g) v
  fmap f x = F f x

Точка этого типа, BTW, должна поддерживать:

read :: In a -> IO a
read (M v) = takeMVar v
read (T v) = atomically (readTVar v)
read (F f v) = f <$> read v

То, что я хочу сделать, это определить очевидный экземпляр Eq для этого типа, например:

instance Eq (In a) where
  (M x) == (M y) = x == y
  (T x) == (T y) = x == y
  (F _ x) == (F _ y) = x == y
  _ == _ = False

Проблема - это третий случай, который терпит неудачу, потому что x и y необязательно имеют один и тот же тип в этой точке. Я это понимаю. В моем собственном коде я могу долго работать, но похоже, что должен быть способ определить Eq напрямую. На мой взгляд, решение похоже на "продолжайте сверление через конструкторы F до тех пор, пока вы не нажмете M или T, тогда, если они будут одним и тем же конструктором (т.е. как M, так и оба T) и того же типа, выполните сравнение равенства", но я Я не знаю, как я мог это написать.

Ответы

Ответ 1

Я очень подозрительно отношусь к вашему равенству, так как он действительно проверяет только половину F, но если это то, что вы действительно хотите, вот как вы можете это сделать. Обратите внимание, что литой служит в качестве теста для равенства типа, так как вы можете сравнивать только два Fs, если типы экзистенциально квантифицированных a внутри одинаковы.

data In a where
  M :: MVar a -> In a
  T :: TVar a -> In a
  F :: (Typeable a) => (a -> b) -> In a -> In b
  deriving (Typeable)


instance Eq (In a) where
  (M x) == (M y) = x == y
  (T x) == (T y) = x == y
  (F _ x) == (F _ y) = Just x == cast y
  _ == _ = False

Или, может быть, вы тоже этого не хотите? Чтение вашей мотивации снова кажется, что вы хотите функцию, где In Int может быть равно In Double.

Как бы вы хотели, чтобы эти два сравнения F floor r и F id r (если r есть M x :: In Double)?

Ответ 2

В какой-то момент вам нужно проверить, равны ли две вещи разного типа. Это можно сделать двумя способами:

  • Класс Typeable.
  • A GADT data Equal a b where Eq :: Equal a a.

Так как MVar и TVar не поддерживают 2, вам нужно будет использовать класс Typeable. Другими словами, вам придется увеличить свой тип данных с помощью ограничений Typeable.

К счастью, у вас есть свобода в отношении того, где положить ограничения. Например, вы можете записать их следующим образом:

data In a where
    M :: Typeable a => MVar a -> In a
    T :: Typeable a => TVar a -> In a
    F :: (a -> b) -> In a -> In b

equal :: In a -> In b -> Bool
equal (M x) (M y)     = Just x == cast y
equal (T x) (T y)     = Just x == cast y
equal (F _ x) (F _ y) = x `equal` y
equal _ _             = False

instance Eq (In a) where
    (==) = equal

Таким образом, вы можете сохранить экземпляр Functor.