Skip to content

Commit d2f66d8

Browse files
authored
Explanation about bidirectionality hints (#71)
1 parent ee8f08b commit d2f66d8

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed
+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
(** * Bidirectionality Hints
2+
3+
*** Summary
4+
5+
An explanation of bidirectionlity hints.
6+
After presenting the requisite background on bidirectional typing and
7+
existential variables in Coq, we will see how bidirectionality hints affect
8+
type checking of function applications, and we will illustrate with simple
9+
examples.
10+
11+
*** Contents
12+
13+
- 1. Bidirectional typing
14+
- 2. Bidirectional typing and existential variables
15+
- 3. Bidirectionality hints
16+
- 4. Examples
17+
18+
*)
19+
20+
(**
21+
Before we explain what bidirectionality hints are,
22+
we must first understand some basics of how type checking works in Coq.
23+
24+
** 1. Bidirectional typing
25+
26+
Coq uses bidirectional typing, an approach to type systems that interleaves
27+
type checking with type inference. A bidirectional type system replaces the
28+
usual typing judgement [e : T] with two judgements: _type checking_
29+
[e ◃ T] where [T] is an input and _type inference_ [e ▹ T]
30+
(also called _type synthesis_ in the literature) where [T] is an output.
31+
Making inputs and outputs explicit enables an algorithmic interpretation of the
32+
typing rules: a bidirectional type system is a way to present a type checking
33+
and a type inference algorithm.
34+
35+
Function application is usually associated with a rule for type inference ([▹]):
36+
37+
[[[
38+
f ▹ forall (x : A), B x a ◃ A
39+
----------------------------------- app-infer
40+
f a ▹ B a
41+
]]]
42+
43+
To infer the type of an application [f a]:
44+
45+
- 1. _infer_ the type of the function [f] ([f ▹ forall (x : A), B x]),
46+
usually [f] is a constant whose type is known from its definition.
47+
- 2. _check_ the arguments [a] against the argument type [A].
48+
- 3. output the result type [B a] as the _inferred_ type for [f a].
49+
50+
What if we want to check with a type, rather than infer it?
51+
The following subsumption rule turns inference ([▹]) into checking ([◃]):
52+
to check that [e] has type [T] ([e ◃ T]),
53+
infer a type [T’] ([e ▹ T’]), then _unify_ [T’] with [T]
54+
([T’ ≡ T]).
55+
56+
[[[
57+
e ▹ T’ T’ ≡ T
58+
----------------- infer-check
59+
e ◃ T
60+
]]]
61+
62+
(Here we gave the subsumption rule using a type equality,
63+
or rather _unification_ ([T’ ≡ T]).
64+
A more general variant of the subsumption rule uses subtyping instead,
65+
and in fact such a generalization lets us deal with coercions in Coq.
66+
For the rest of this explanation, we will ignore coercions
67+
and only talk about unification ([T’ ≡ T]) to keep it simple.)
68+
69+
The two rules above combine into the following rule for checking an application:
70+
71+
[[[
72+
f ▹ forall (x : A), B x
73+
a ◃ A B a ≡ T
74+
------------------------ (1)
75+
f a ◃ T
76+
]]]
77+
78+
To check whether the application [f a] has a given type [T]:
79+
80+
- 1. _infer_ the type of [f];
81+
- 2. _check_ the argument [a] against its type [A];
82+
- 3. _unify_ the inferred result type [B a] and the checked one [T].
83+
84+
** 2. Bidirectional typing and existential variables
85+
86+
To a first approximation, the unification judgement ([T’ ≡ T]) merely
87+
denotes an equivalence relation between types.
88+
89+
This story is further complicated in Coq by the presence of
90+
{{https://coq.inria.fr/doc/V8.20.0/refman/language/extensions/evars.html} existential variables}.
91+
To avoid fully spelling out all terms, in Coq, you can write
92+
wildcards ([_]) instead and Coq will try to infer them. Wildcards are replaced
93+
with fresh existential variables with names like [?u] during pretyping.
94+
Existential variables will then be instantiated by unification:
95+
when a unification problem such as [?u ≡ T] is encountered, Coq will
96+
use this information to instantiate the variable [?u].
97+
98+
Thus, the typing rules above are interpreted statefully: there is a global set of
99+
bindings for existential variables [?u := M] which is read and updated during
100+
unification. In particular, the order in which unification judgements [T’ ≡ T]
101+
are evaluated matters.
102+
103+
For example, consider the following two unification requests:
104+
[[
105+
?u ≡ true
106+
0 ≡ if ?u then 0 else 1
107+
]]
108+
109+
Unifying [?u ≡ true] first allows the second unification to succeed since
110+
[if true 0 else 1] reduces to [0]. In contrast, unifying [0 ≡ if ?u then 0 else 1]
111+
will fail if we haven't instantiated [?u] yet, because [if ?u then 0 else 1]
112+
is stuck.
113+
114+
** 3. Bidirectionality hints
115+
116+
Bidirectionality hints indicate when unification with the result type should happen
117+
during type-checking of a function application. By default, that happens
118+
at the end, after having type-checked all of the arguments. This corresponds
119+
to the type checking rule above.
120+
121+
A bidirectionality hint moves that unification step earlier.
122+
Intuitively, that allows information about a function expected result type
123+
to be propagated to type check its arguments.
124+
125+
For instance, to type check the application [f _ b] of a polymorphic function
126+
[f : forall (x : A), B x -> C x], knowing that the result type is [C T]
127+
for some [T], we may want to infer that the first argument [_] should be [T]
128+
and check that the second argument [b] has type [B T]. However, by default,
129+
without a bidirectionality hint, the result type [C T] will be ignored at first,
130+
and [b] will be checked against a type [B ?x] containing less known information.
131+
132+
Earlier, we showed the type checking rule for a function application with one argument.
133+
While function applications with multiple arguments [f a b] are technically
134+
multiple nested applications [(f a) b],
135+
for the purposes of type checking in Coq, they are treated as a single n-ary application.
136+
For example, the rule for type checking an application with two arguments [f a b] looks like this:
137+
138+
[[[
139+
f ▹ forall (x : A), B x -> C x
140+
a ◃ A b ◃ B a C a ≡ T
141+
------------------------------- (2)
142+
f a b ◃ T
143+
]]]
144+
145+
(For more generality, you could reformulate that typing rule with
146+
the more dependent function type [forall (x : A) (y : B x), C x y],
147+
and also with more arguments. We will stick to this simpler form
148+
as our running example.)
149+
150+
In other words, we check whether the application [f a] has a given type [T]
151+
following these steps:
152+
153+
- 1. infer the type of [f];
154+
- 2. check the first argument [a] against its type [A] contained in the type of [f];
155+
- 3. check the second argument [b] against its type [B a]
156+
(that is [B x] from the type of [f] with [x] replaced by [a]);
157+
- 4. unify the result type [C a] and [T].
158+
159+
The unification of the result types is the last step by default.
160+
That behavior can be modified using bidirectionality hints.
161+
162+
_Bidirectionality hints_ are declared using the [Arguments] command,
163+
as a [&] symbol. For example:
164+
[[
165+
Arguments f _ & _.
166+
]]
167+
168+
This hint [f _ & _] makes unification happen after type checking the first
169+
argument, instead of at the end, resulting in the following type checking rule:
170+
171+
[[[
172+
f ▹ forall (x : A), B x -> C x
173+
a ◃ A C a ≡ T b ◃ B a
174+
----------------------------- (2)
175+
f a b ◃ T
176+
]]]
177+
*)
178+
179+
(** ** 4. Examples *)
180+
181+
Section Example.
182+
183+
(** Let us demonstrate the difference with two examples: one does not require the hint, the other does.
184+
Both use the same concrete definitions for the types [A], [B], and [C] below,
185+
where [B] and [C] pattern match on the first argument [x : A]: *)
186+
187+
Let A : Type := bool.
188+
Let B (x : A) : Type := if x then bool else nat.
189+
Let C : A -> Type := B.
190+
191+
Variable f : forall (x : A), B x -> C x.
192+
193+
(** *)
194+
195+
Section Example1.
196+
197+
(** The expression [f _ (0 : B false) : nat] type checks without
198+
bidirectionality hints, but fails with the hint [f _ & _]. *)
199+
200+
(** With no bidirectionality hint, type checking [f _ (0 : B false) ◃ nat] proceeds as follows:
201+
- 0. generate a fresh existential variable [?x] in place of the wildcard [_];
202+
- 1. check the first argument [?x ◃ bool]: do nothing
203+
(actually, [bool] gets unified with the type of [?x], which is another fresh existential variable,
204+
but that is not an important detail for this explanation);
205+
- 2. check the second argument [(0 : B false) ◃ B ?x]: the type annotation gives us the inferred type [B false],
206+
which is unified with the checked type [B ?x], and we unify [false] with [?x] (note that this unification step happens
207+
even if [B] is not injective: in general, [B false ≡ B ?x] does not imply [false ≡ ?x];
208+
unification in Coq is lossy, it does not aim to find most general unifiers);
209+
- 3. unify the result types [C ?x ≡ nat]: it succeeds because we previously unified [?x] with [false].
210+
*)
211+
212+
(* No bidirectionality hint. *)
213+
Check (f _ (0 : B false) : nat).
214+
215+
(** With a bidirectionality hint, type checking proceeds as follows:
216+
- 1. check the first argument [?x ◃ bool]: same as before;
217+
- 2. unify the result types [C ?x ≡ nat], that is [(if ?x then bool else nat) ≡ nat],
218+
which fails because [?x] is still unknown. *)
219+
220+
(* Add bidirectionality hint. *)
221+
Arguments f _ & _.
222+
Fail Check (f _ (0 : B false) : nat).
223+
224+
(** *)
225+
226+
End Example1.
227+
228+
(** *)
229+
230+
Section Example2.
231+
232+
(** The expression [f _ 0 : C false] type checks with the hint
233+
[f _ & _] but fails without. *)
234+
235+
(** With no bidirectionality hint:
236+
- 1. check the first argument [?x ◃ bool]: same as before;
237+
- 2. check the second argument [0 ◃ B ?x]: [0] is a constant so its type can be inferred as [nat],
238+
which is unified with the checked type [B ?x], and that fails because [?x] is still unknown. *)
239+
240+
(* No bidirectionality hint. *)
241+
Fail Check (f _ 0 : C false).
242+
243+
(** With the bidirectionality hint:
244+
- 1. check the first argument [?x ◃ bool]: same as before;
245+
- 2. unify the result types [C ?x ≡ C false], which unifies [?x ≡ false];
246+
- 3. check the second argument [0 ◃ B ?x]: its type is inferred as [nat],
247+
which is unified with the checked type [B ?x],
248+
and that succeeds we previously unified [?x] with [false]. *)
249+
250+
(* Add bidirectionality hint. *)
251+
Arguments f _ & _.
252+
Check (f _ 0 : C false).
253+
254+
(** *)
255+
256+
End Example2.
257+
258+
(** *)
259+
260+
End Example.

src/index.html

+6
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ <h3>Coq's Theory</h3>
146146
and
147147
<a href="Explanation_Template_Polymorphism.v">source code</a>
148148
</li>
149+
<li>
150+
Explanation of bidirectionality hints:
151+
<a href="Explanation_Bidirectionality_Hints.html">interactive version</a>
152+
and
153+
<a href="Explanation_Bidirectionality_Hints.v">source code</a>
154+
</li>
149155
</ul>
150156

151157
<h2 id=eq-tut>Equations Tutorials</h2>

0 commit comments

Comments
 (0)