Bidirectionality Hints
Summary
Contents
- 1. Bidirectional typing
- 2. Bidirectional typing and existential variables
- 3. Bidirectionality hints
- 4. Examples
1. Bidirectional typing
f ▹ forall (x : A), B x a ◃ A | app-infer |
f a ▹ B a |
- 1. infer the type of the function f (f ▹ ∀ (x : A), B x), usually f is a constant whose type is known from its definition.
- 2. check the arguments a against the argument type A.
- 3. output the result type B a as the inferred type for f a.
e ▹ T’ T’ ≡ T | infer-check |
e ◃ T |
f ▹ forall (x : A), B x | |
a ◃ A B a ≡ T | (1) |
f a ◃ T |
- 1. infer the type of f;
- 2. check the argument a against its type A;
- 3. unify the inferred result type B a and the checked one T.
2. Bidirectional typing and existential variables
?u ≡ true
0 ≡ if ?u then 0 else 1
3. Bidirectionality hints
f ▹ forall (x : A), B x -> C x | |
a ◃ A b ◃ B a C a ≡ T | (2) |
f a b ◃ T |
- 1. infer the type of f;
- 2. check the first argument a against its type A contained in the type of f;
- 3. check the second argument b against its type B a (that is B x from the type of f with x replaced by a);
- 4. unify the result type C a and T.
Arguments f _ & _.
f ▹ forall (x : A), B x -> C x | |
a ◃ A C a ≡ T b ◃ B a | (2) |
f a b ◃ T |
4. Examples
Let us demonstrate the difference with two examples: one does not require the hint, the other does.
Both use the same concrete definitions for the types A, B, and C below,
where B and C pattern match on the first argument x : A:
Let A : Type := bool.
Let B (x : A) : Type := if x then bool else nat.
Let C : A → Type := B.
Variable f : ∀ (x : A), B x → C x.
The expression f _ (0 : B false) : nat type checks without
bidirectionality hints, but fails with the hint f _ & _.
With no bidirectionality hint, type checking f _ (0 : B false) ◃ nat proceeds as follows:
- 0. generate a fresh existential variable ?x in place of the wildcard _;
- 1. check the first argument ?x ◃ bool: do nothing (actually, bool gets unified with the type of ?x, which is another fresh existential variable, but that is not an important detail for this explanation);
- 2. check the second argument (0 : B false) ◃ B ?x: the type annotation gives us the inferred type B false, which is unified with the checked type B ?x, and we unify false with ?x (note that this unification step happens even if B is not injective: in general, B false ≡ B ?x does not imply false ≡ ?x; unification in Coq is lossy, it does not aim to find most general unifiers);
- 3. unify the result types C ?x ≡ nat: it succeeds because we previously unified ?x with false.
With a bidirectionality hint, type checking proceeds as follows:
- 1. check the first argument ?x ◃ bool: same as before;
- 2. unify the result types C ?x ≡ nat, that is (if ?x then bool else nat) ≡ nat, which fails because ?x is still unknown.
The expression f _ 0 : C false type checks with the hint
f _ & _ but fails without.
With no bidirectionality hint:
- 1. check the first argument ?x ◃ bool: same as before;
- 2. check the second argument 0 ◃ B ?x: 0 is a constant so its type can be inferred as nat, which is unified with the checked type B ?x, and that fails because ?x is still unknown.
With the bidirectionality hint:
- 1. check the first argument ?x ◃ bool: same as before;
- 2. unify the result types C ?x ≡ C false, which unifies ?x ≡ false;
- 3. check the second argument 0 ◃ B ?x: its type is inferred as nat, which is unified with the checked type B ?x, and that succeeds we previously unified ?x with false.