Haskell DerivingVia на многопараметрических классах с увлекательными играми - PullRequest
0 голосов
/ 26 октября 2018

Я пытаюсь использовать DerivingVia, чтобы вырезать шаблон для определения экземпляров для класса многопараметрического типа с функциональными зависимостями.

У меня есть эти типы и класс:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DerivingVia #-}

newtype Wrapper t = Wrapper t  
newtype Wrapper2 t = Wrapper2 t

class MyEq a f | a -> f where
  eq :: a -> a -> f Bool

-- Instance for 'Wrapper2'
instance Eq t => MyEq (Wrapper2 t) Wrapper2 where
  eq (Wrapper2 t) (Wrapper2 t') = Wrapper2 (t == t')

Я хочу получить MyEq (Wrapper Int) Wrapper, используя deriving via.

Моя первая попытка была использовать:

deriving via Wrapper2 instance MyEq (Wrapper Int) Wrapper

Как обсуждалось в разделе 6.2 статьи, https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf, этоищет экземпляр MyEq (Wrapper Int) Wrapper2, второй аргумент был «изменен», но первый по-прежнему Wrapper Int.

Очевидно, instance MyEq (Wrapper Int) Wrapper2 не существует, поскольку я реализовал instance MyEq (Wrapper2 Int) Wrapper2.

Я не могу «обмануть», создав (см. Wrapper в качестве аргумента первого типа):

-- Instance for 'Wrapper2'
instance Eq t => MyEq (Wrapper t) Wrapper2 where
  eq (Wrapper2 t) (Wrapper2 t') = Wrapper2 (t == t')

Поскольку в этом случае функциональная зависимость Wrapper t -> Wrapper2 не соблюдается.

Я могулегко решить проблему, переписав eq :: f a -> f a -> f Bool и удалив функциональную зависимость, но я бы не хотел менять этот API.

1 Ответ

0 голосов
/ 26 октября 2018

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

instance MyEq (Wrapper Int) Wrapper where
  eq (Wrapper t) (Wrapper t') = Wrapper (t == t')

Я не могу найти способ вывести класс именно так, как вы хотите, потому чтокак вы сами наблюдаете, это требует от вас изменения обоих параметров класса, но в настоящее время мы можем получить только последние.

Одна из возможностей состоит в том, чтобы перевернуть аргументы класса, чтобы "важный" параметр класса (тот, которыйкоторый определяет другое) становится последним, а затем настраивает тип обертки, который вы выводите, чтобы включить некоторую полезную информацию, например:

class MyEq f a | a -> f where
  aeq :: a -> a -> f Bool

Функция aeq сохраняет тот же тип, но аргументы классаMyEq перевернуты.Теперь Wrapper2 получает дополнительный параметр, позволяющий нам указать желаемое значение f при получении:

newtype Wrapper2 (f :: Type -> Type) t = Wrapper2 t

Теперь экземпляр для Wrapper2 можно определить без явного указания f:

instance (Eq t, Coercible Bool (f Bool)) => MyEq f (Wrapper2 f t) where
  eq (Wrapper2 t) (Wrapper2 t') = coerce (t == t')

Дополнительный параметр в Wrapper2 необходим здесь для удовлетворения функциональной зависимости.

Теперь мы можем получить желаемый экземпляр следующим образом:

deriving via Wrapper2 Wrapper Int instance MyEq Wrapper (Wrapper Int)

Это работаетпотому что теперь GHC ищет instance MyEq Wrapper (Wrapper2 Wrapper Int), и это соответствует предоставленному нами.


Вы можете достичь того же, используя связанный тип:

class MyEq a where
  type Result a :: Type -> Type
  eq :: a -> a -> Result a Bool

То жеопределение Wrapper2 с дополнительным аргументом.Экземпляр становится

instance (Eq t, Coercible Bool (f Bool)) => MyEq (Wrapper2 f t) where
  type Result (Wrapper2 f t) = f
  eq (Wrapper2) (Wrapper2 t') = coerce (t == t')

deriving via Wrapper2 Wrapper Int instance MyEq (Wrapper Int)
...