Начиная с вашего примера
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds, KindSignatures #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wall #-}
import Data.Type.Equality
data Car
data Food
data CarRental = CarRental {passengers :: Int, mileage :: Int}
deriving (Eq, Ord)
data ErrandList = ErrandList {avoidJunkFood :: Bool}
deriving (Eq, Ord)
data GetStuff a where
RentACar :: CarRental -> GetStuff Car
BuyFood :: ErrandList -> GetStuff Food
data Some t = forall a. Some (t a)
Вам нужно будет написать экземпляр http://hackage.haskell.org/package/dependent-sum-0.4/docs/Data-GADT-Compare.html#t:GEq
class GEq f where
geq :: f a -> f b -> Maybe (a :~: b)
Тогда вы сможете определить экземпляр Eq (Some f)
instance GEq f => Eq (Some f) where
Some fa == Some fb = case geq fa fb of
Just Refl -> True
Nothing -> False
Написание экземпляра вручную повторяется, но не ужасно.Обратите внимание, что я написал способом без "catch all" в последнем случае.
instance GEq GetStuff where
geq (RentACar x) z = case z of
RentACar x' -> if x == x' then Just Refl else Nothing
_ -> Nothing
geq (BuyFood x) z = case z of
BuyFood x' -> if x == x' then Just Refl else Nothing
_ -> Nothing
Для Ord
GADT существует класс GCompare
.
Таким образом, проблема сводится к тому, "как"получить GEq
или GCompare
автоматически ".Я думаю, что для специальных GADT, таких как ваш GetStuff
, вы можете написать быструю и грязную TH, чтобы сгенерировать код.
Generic
-подобная альтернатива, о которой я могу подумать, потребует от вас написания преобразованияфункции от и до GetStuff
, что может быть выгодно, если вам нужно написать более общие функции.Давайте также исследуем это.Сначала мы определяем общее представление интересующих нас GADT:
data Sum (cs :: [(*, *)]) a where
Z :: a :~: c -> b -> Sum ( '(c, b) ': cs) a
S :: Sum cs a -> Sum (c ': cs) a
Мы можем конвертировать между GetStuff
и Sum
.То, что нам нужно написать для каждого GADT, это O (n) , где n
- количество конструкторов.
type GetStuffCode =
'[ '(Car, CarRental)
, '(Food, ErrandList)
]
toSum :: GetStuff a -> Sum GetStuffCode a
toSum (RentACar x) = Z Refl x
toSum (BuyFood x) = S (Z Refl x)
fromSum :: Sum GetStuffCode a -> GetStuff a
fromSum (Z Refl x) = RentACar x
fromSum (S (Z Refl x)) = BuyFood x
fromSum (S (S x)) = case x of {} -- silly GHC requires this :)
Теперь, когда у нас есть общее представление, Sum
мы можем написать универсальные функции.Equality, GGEq
для Generic Gadt Equality Класс выглядит как GEq
, но мы используем Sum
в качестве аргументов.
class GGEq code where
ggeq :: Sum code a -> Sum code b -> Maybe (a :~: b)
Нам понадобятся два экземпляра, дляноль и минусы коды :
instance GGEq '[] where
ggeq x _ = case x of {}
instance (Eq b, '(x, b) ~ c, GGEq cs) => GGEq (c ': cs) where
ggeq (Z Refl x) (Z Refl y) = if x == y then Just Refl else Nothing
ggeq (S x) (S y) = ggeq x y
ggeq (Z _ _) (S _) = Nothing
ggeq (S _) (Z _ _) = Nothing
Используя этот механизм, написание geq
для GetStuff
тривиально:
geq1 :: GetStuff a -> GetStuff b -> Maybe (a :~: b)
geq1 x y = ggeq (toSum x) (toSum y)