Bonjour à tous,
J'ai récemment travaillé à ajouter l'élimination dépendante à Typer
(4c632a1f, 8a7b7910), qui repose sur des égalités dans les expressions
`case`. Pour les utiliser, on élimine éventuellement ces égalités avec
`Eq.cast` (c'est ce que fait la macro `case_return_`, d'ailleurs). On
dirait que dans certains cas de son utilisation (au niveau des types),
il serait utile que les appels à `Eq.cast` puissent être réduits dans
le calcul des WHNF, ce qui n'est pas le cas en ce moment.
Je me pose quelques questions quant à l'implantation de la WHNF de
`Eq.cast`, sur lesquelles j'apprécierais vos commentaires. Aussi, pour
rendre la discussion plus concrète, j'ai construit un petit exemple où
l'unification échoue quand la réduction bloque sur un appel à `Eq.cast`.
Pour obtenir le code de l'exemple à partir de ce courriel, exécutez :
$ sed -n 's/^\s*> \?\(.*\)/\1/p' <email.txt >exemple.typer
1. Question
Je me demande comment implanter la WHNF de `Eq.cast`, j'ai
l'impression, intuitivement, qu'il suffit de vérifier que son
argument `p` est bel et bien convertible à `Eq.refl` (autrement
dit, ce n'est pas simplement une variable/hypothèse), auquel cas,
il me paraît correct d'ignorer l'argument `f` et de se réduire à
l'argument explicite.
Évidemment, si `p` n'est pas convertible à `Eq.refl`, la réduction
doit bloquer.
Est-ce que cette approche peut causer des problèmes au niveau
théorique?
2. Exemple
Utilisons, au niveau des types, `decideBoolEq`, une fonction que
l'on définit avec l'élimination dépendante, pour illustrer le
problème.
Petite parenthèse, je vais utiliser le `case` builtin pour
simplifier les affichages:
> case_ = ##case_;
Prenons le type `Decidable` défini de cette façon:
>
> Decidable =
> typecons (Decidable (ℓ ::: TypeLevel) (prop : Type_ ℓ))
> (yes (p :: prop)) (no (p :: Not prop));
> yes = datacons Decidable yes;
> no = datacons Decidable no;
>
Définissons `decideBoolEq`, qui prouve la décidabilité de l'égalité
de booléens. On se sert des lemmes `true≠false` et `false≠true`
qu'on définit au passage.
>
> true≠false (p : Eq true false) =
> Eq_cast (p := p)
> (f := lambda tf -> case tf
> | true => True
> | false => False)
> (); % This is unit, an inhabitant of `True = Unit`.
>
> false≠true (p : Eq false true) = true≠false (Eq_comm p);
>
> decideBoolEq : (a : Bool) => (b : Bool) => Decidable (Eq a b);
> decideBoolEq =
> lambda (a : Bool) (b : Bool) =>
> case a return (Decidable (Eq a b))
> | true =>
> (case b return (Decidable (Eq true b))
> | true => yes (p := Eq_refl)
> | false => no (p := true≠false))
> | false =>
> (case b return (Decidable (Eq false b))
> | true => no (p := false≠true)
> | false => yes (p := Eq_refl));
>
Note: Ici, on utilise la macro `case_return_`, qui insère les bons
appels à `Eq.cast`.
Définissons maintenant une méthode d'accès pour obtenir la preuve
d'un `Decidable`, qu'il soit vrai ou faux:
>
> getProof :
> (l : TypeLevel) ≡>
> (prop : Type_ l) ≡>
> (d : Decidable prop) ->
> case d | yes => prop | no => Not prop;
> getProof d =
> case d return (case d | yes => prop | no => Not prop)
> | yes (p := p) => p
> | no (p := p) => p;
>
Ces définitions nous permettent de définir un terme qui fait
échouer l'élaboration:
>
> this_fails =
> (getProof (decideBoolEq : Decidable (Eq true true)) : Eq true true);
>
Voici l'erreur qu'on obtient:
% ./samples/exemple.typer:48:9:Error:(ELAB) Type mismatch (residue)! Context expected:
% (Eq[257] ##TypeLevel.z typecons (Bool) (false) (true) true[210] true[210])
% but expression has type:
% case (decideBoolEq[1] datacons Bool[211] true datacons Bool[211] true) | no _ => (Not[53] ##TypeLevel.z (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] true[212])) | yes _ => (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] true[212])
% can't unify:
% (Eq[257] ##TypeLevel.z typecons (Bool) (false) (true) true[210] true[210])
% with:
% case (decideBoolEq[1] datacons Bool[211] true datacons Bool[211] true) | no _ => (Not[53] ##TypeLevel.z (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] true[212])) | yes _ => (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] true[212])
% > Call: (getProof[0] ##TypeLevel.z (Eq[257] ##TypeLevel.z typecons (Bool) (false) (true) true[210] true[210]) (decideBoolEq[1] datacons Bool[211] true datacons Bool[211] true))
%
Pourquoi? On doit unifier le type de
getProof (decideBoolEq : Decidable (Eq true true))
qui est
case (decideBoolEq (a := true) (b := true))
| yes => Eq true true
| no => Not (Eq true true)
avec `Eq true true`. Pour que cette unification fonctionne, il faut
que `decideBoolEq (a := true) (b := true)` se réduise à un
constructeur de `Decidable`, en l'occurence `yes`. Or, quand on
regarde la première branche de `decideBoolEq`, on se rend compte
qu'à cause de l'élimination dépendante, on obtient plutôt un appel
à `Eq.cast`. Effectivement, en déboggant, j'obtiens le terme:
% (##Eq.cast
% ##TypeLevel.z
% ##TypeLevel.z
% typecons (Bool) (false) (true)
% true[210]
% datacons Bool[211] true
% (##Eq.refl
% ##TypeLevel.z
% Bool[211]
% datacons Bool[211] true)
% lambda (a : typecons (Bool) (false) (true)) ->
% (Decidable[7]
% ##TypeLevel.z
% (Eq[258] ##TypeLevel.z typecons (Bool) (false) (true) a[0] datacons Bool[212] true))
% case datacons Bool[211] true
% | false => (##Eq.cast ##TypeLevel.z ##TypeLevel.z typecons (Bool) (false) (true) false[210] datacons Bool[212] true <anon>[0] lambda (b : typecons (Bool) (false) (true)) -> (Decidable[8] ##TypeLevel.z (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] b[0])) (no[5] ##TypeLevel.z (Eq[258] ##TypeLevel.z typecons (Bool) (false) (true) true[211] false[210]) true≠false[4]))
% | true => (##Eq.cast
% ##TypeLevel.z
% ##TypeLevel.z
% typecons (Bool) (false) (true)
% true[211]
% datacons Bool[212] true
% <anon>[0]
% lambda (b : typecons (Bool) (false) (true)) ->
% (Decidable[8] ##TypeLevel.z
% (Eq[259] ##TypeLevel.z typecons (Bool) (false) (true) true[212] b[0]))
% (yes[6]
% ##TypeLevel.z
% (Eq[258] ##TypeLevel.z typecons (Bool) (false) (true) datacons Bool[212] true datacons Bool[212] true)
% (Eq_refl[216] ##TypeLevel.z typecons (Bool) (false) (true) datacons Bool[212] true))))
On constate que le `yes` que l'on cherche se trouve sous deux
niveaux de `Eq.cast` à cause des deux niveaux de `case_return_`
dans `decideBoolEq`. Ces deux appels à `Eq.cast` devraient pouvoir
être réduits!
Jean-Alexandre