Вот некоторые стратегии, которые не требуют каких-либо расширений, но обменивают некоторые первоначальные затраты на простоту получения этих классов.
Обратите внимание, что, поскольку 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)