Is there an easy way to write equality (DecEq
) instances for data types? For example, I'd like the following to have O(n) lines in its DecEq
declaration, where ?p
is something simple:
data Foo = A | B | C | D
instance [syntactic] DecEq Foo where
decEq A A = Yes Refl
decEq B B = Yes Refl
decEq C C = Yes Refl
decEq D D = Yes Refl
decEq _ _ = No ?p
David Christiansen is working on something to automate this in general, and he is essentially finished; it can be found in his GitHub repository. In the mean time, here's an approach that can take you from O(n^2) cases to O(n) cases in this situation. First, some preliminaries. If you have something with decidable equality, and you have an injection from the type of your choice to that type, then you can make a decision procedure for that type:
IsInjection : (a -> b) -> Type
IsInjection {a} f = (x,y : a) -> f x = f y -> x = y
decEqInj : DecEq d => (tToDec : t -> d) ->
(isInj : IsInjection tToDec) ->
(p, q : t) -> Dec (p = q)
decEqInj tToDec isInj p q with (decEq (tToDec p) (tToDec q))
| (Yes prf) = Yes (isInj p q prf)
| (No contra) = No (\pq => contra (cong pq))
Unfortunately, directly proving that your function is an injection gets you back to O(n^2) cases, but it's generally the case that any function with a retraction is injective:
retrInj : (f : d -> t) -> (g : t -> d) ->
((x : t) -> f (g x) = x) ->
IsInjection g
retrInj f g prf x y gxgy =
let fgxfgy = cong {f} gxgy
foo = sym $ prf x
bar = prf y
in trans foo (trans fgxfgy bar)
Thus if you have a function from the type of your choice to one with decidable equality and a retraction for it, then you have decidable equality for your type:
decEqRet : DecEq d => (decToT : d -> t) ->
(tToDec : t -> d) ->
(isRet : (x : t) -> decToT (tToDec x) = x) ->
(p, q : t) -> Dec (p = q)
decEqRet decToT tToDec isRet p q =
decEqInj tToDec (retrInj decToT tToDec isRet) p q
Finally, you can write the cases for what you've chosen:
data Foo = A | B | C | D
natToFoo : Nat -> Foo
natToFoo Z = A
natToFoo (S Z) = B
natToFoo (S (S Z)) = C
natToFoo _ = D
fooToNat : Foo -> Nat
fooToNat A = 0
fooToNat B = 1
fooToNat C = 2
fooToNat D = 3
fooNatFoo : (x : Foo) -> natToFoo (fooToNat x) = x
fooNatFoo A = Refl
fooNatFoo B = Refl
fooNatFoo C = Refl
fooNatFoo D = Refl
instance DecEq Foo where
decEq x y = decEqRet natToFoo fooToNat fooNatFoo x y
Note that while the natToFoo
function has somewhat large patterns, there isn't really much going on there. It should probably be possible to make the patterns small by nesting them, although that may be ugly.
Generalization: At first I thought this would only work for special cases, but I now think it may be a little better than that. In particular, if you have an algebraic datatype holding types with decidable equality, you should be able to convert it into a nested Either
of nested Pair
, which will get you there. For instance (using Maybe
to shorten Either (Bool, Nat) ()
):
data Fish = Cod Int | Trout Bool Nat | Flounder
watToFish : Either Int (Maybe (Bool, Nat)) -> Fish
watToFish (Left x) = Cod x
watToFish (Right Nothing) = Flounder
watToFish (Right (Just (a, b))) = Trout a b
fishToWat : Fish -> Either Int (Maybe (Bool, Nat))
fishToWat (Cod x) = Left x
fishToWat (Trout x k) = Right (Just (x, k))
fishToWat Flounder = Right Nothing
fishWatFish : (x : Fish) -> watToFish (fishToWat x) = x
fishWatFish (Cod x) = Refl
fishWatFish (Trout x k) = Refl
fishWatFish Flounder = Refl
instance DecEq Fish where
decEq x y = decEqRet watToFish fishToWat fishWatFish x y
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With