David Christiansen sta lavorando a qualcosa per automatizzare questo, in generale, e lui è essenzialmente finito; può essere trovato in his GitHub repository. Nel frattempo, ecco un approccio che può portarti da casi O (n^2) a casi O (n) in questa situazione. Innanzitutto, alcuni preliminari. Se avete qualcosa con l'uguaglianza decidibile, e si dispone di un'iniezione dal tipo di vostra scelta per quel tipo, allora si può fare una procedura di decisione per quel tipo:
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))
Purtroppo, direttamente dimostrando che la funzione è un iniezione si torna a O (n^2) casi, ma è generalmente il caso che ogni funzione con una ritrattazione è iniettiva:
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)
Quindi se avete una funzione dal tipo di vostra scelta per uno con decidibile uguaglianza e un ritiro per esso, quindi avete uguaglianza decidibile per il vostro tipo:
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
Infine, è possibile scrivere i casi per quello che hai scelto:
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
notare che, mentre la funzione natToFoo
ha un po 'di grandi modelli, non c'è davvero molto succedendo lì. Probabilmente dovrebbe essere possibile rendere i disegni piccoli annidandoli, anche se potrebbe essere brutto.
Generalizzazione: all'inizio pensavo che avrebbe funzionato solo per casi speciali, ma ora penso che potrebbe essere un po 'meglio di così. In particolare, se si dispone di un tipo di dati algebrico contenente tipi con uguaglianza decidibile, si dovrebbe essere in grado di convertirlo in un nidificato Either
di nidificato Pair
, che vi porterà lì. Per esempio (usando Maybe
per accorciare 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
Agda ha anche questo [nella libreria standard] (https://agda.github.io/agda-stdlib/Data.Nat.html#2027). Puoi scrivere un combinatore, che permette di costruire facilmente funzioni come 'natToFoo': http://lpaste.net/133099. – user3237465