Извлечь экземпляр из поля в записи - PullRequest
5 голосов
/ 03 мая 2019

Пример кода:

{-# LANGUAGE NamedFieldPuns #-}

module Sample where

class Sample a where
  isA :: a -> Bool
  isB :: a -> Bool
  isC :: a -> Bool

data X =
  X

instance Sample X where
  isA = undefined
  isB = undefined
  isC = undefined

data Wrapper = Wrapper
  { x :: X
  , i :: Int
  }

instance Sample Wrapper where
  isA Wrapper {x} = isA x
  isB Wrapper {x} = isB x
  isC Wrapper {x} = isC x

Здесь у меня есть некоторый класс, который реализуется X, а затем еще одна запись Wrapper, содержащая X.

Я хочу, чтобы Wrapper получил экземпляр Sample через его поле x.

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

Есть ли какой-нибудь флаг или какой-либо метод, чтобы сделать это автоматически или только один раз?

Это похоже на DerivingVia и GeneralisedNewtypeDeriving, но оба они нацелены на newtype или только на принудительные типы

1 Ответ

4 голосов
/ 03 мая 2019

Вот некоторые стратегии, которые не требуют каких-либо расширений, но обменивают некоторые первоначальные затраты на простоту получения этих классов.

Обратите внимание, что, поскольку Sample не является новым типом, нет гарантии, что он будет содержать только одну X, а не две, более или переменную сумму (Maybe X? Either X X?). Следовательно, как вы увидите, ваши опции должны сделать выбор X внутри структуры явным, и это вероятная причина для расширения, которое автоматически выводит это значение для , а не .

Вывести одну функцию вместо многих

Чтобы удовлетворить Sample, нам действительно нужен X. Давайте сделаем это типом класса:

class HasX t where
  getX :: t -> X

class Sample t where
  isA :: t -> Bool
  isB :: t -> Bool
  isC :: t -> Bool
  default isA :: HasX t => t -> Bool
  isA = isA . getX
  default isB :: HasX t => t -> Bool
  isB = isB . getX
  default isC :: HasX t => t -> Bool
  isC = isC . getX

instance HasX Wrapper where
  getX = x

instance Sample Wrapper -- no implementation necessary

Получать через дженерики

Допустим, мы хотим работать только с записями, в которых X является первым полем. Чтобы соответствовать структуре типов, мы можем использовать GHC.Generics . Здесь мы добавляем путь к HasX по умолчанию для первого поля:

class HasX t where
  getX :: t -> X
  default getX :: (Generic a, HasX (Rep a)) => t -> X
  getX = getX . from

instance HasX (M1 D d (M1 C c (M1 S s (Rec0 X) :*: ff))) o where
  getX (M1 (M1 ((M1 (K1 x)) :*: _))) = x

Последний экземпляр для HasX сопоставляет любую запись (M1 D) с одним конструктором (M1 C), который имеет более одного (:*:) поля (M1 S), причем первое поле имеет тип (Rec0) X.

(Да, универсальный экземпляр громоздкий. Редактирование приветствуется.)

(Чтобы увидеть точное представление универсального типа Wrapper, проверьте Rep Wrapper в консоли GHCi.)

Теперь экземпляр для Wrapper можно записать как:

data Wrapper = Wrapper
  { x :: X
  , i :: Int
  }
  deriving (Generic, HasX, Sample)
...