Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Easy Syntactic Equality in Idris

Tags:

idris

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
like image 431
Dennis Griffith Avatar asked May 19 '15 00:05

Dennis Griffith


1 Answers

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
like image 77
dfeuer Avatar answered Jan 01 '23 22:01

dfeuer