@@ -16,43 +16,45 @@ a set of region constraints, which we'll ignore in this introduction.
16
16
17
17
There are often many, or even infinitely many, solutions to a query. For
18
18
example, say we want to prove that ` exists<T> { Vec<T>: Debug } ` for _ some_
19
- type ` ?T ` . Our solver should be capable of iterating over each answer one at
20
- a time, say ` ?T = u32 ` , then ` ?T = i32 ` , and so on, rather than iterating
21
- over every type in the type system. If we need more answers, we can request
22
- more until we are done. This is similar to how Prolog works.
19
+ type ` ?T ` . Our solver should be capable of yielding one answer at a time, say
20
+ ` ?T = u32 ` , then ` ?T = i32 ` , and so on, rather than iterating over every type
21
+ in the type system. If we need more answers, we can request more until we are
22
+ done. This is similar to how Prolog works.
23
23
24
24
* See also: [ The traditional, interactive Prolog query] [ pq ] *
25
25
26
26
[ pq ] : ./canonical-queries.html#the-traditional-interactive-prolog-query
27
27
28
28
### Breadth-first
29
29
30
- ` Vec<?T>: Debug ` is true if ` ?T: Debug ` . This leads to a cycle: `[ Vec<u32 > ,
30
+ ` Vec<?T>: Debug ` is true if ` ?T: Debug ` . This leads to a cycle: `[ Vec<u32 >,
31
31
Vec<Vec<u32 >>, Vec<Vec<Vec<u32 >>>] ` , and so on all implement ` Debug`. Our
32
32
solver ought to be breadth first and consider answers like `[ Vec<u32 >: Debug,
33
33
Vec<i32 >: Debug, ...] ` before it recurses, or we may never find the answer
34
34
we're looking for.
35
35
36
- ### Cache friendly
36
+ ### Cachable
37
37
38
38
To speed up compilation, we need to cache results, including partial results
39
39
left over from past solver queries.
40
40
41
41
## Description of how it works
42
42
43
- The basis of the solver is the ` Forest ` type. A * forest* stores a
43
+ The basis of the solver is the [ ` Forest ` ] type. A * forest* stores a
44
44
collection of * tables* as well as a * stack* . Each * table* represents
45
45
the stored results of a particular query that is being performed, as
46
46
well as the various * strands* , which are basically suspended
47
47
computations that may be used to find more answers. Tables are
48
48
interdependent: solving one query may require solving others.
49
49
50
+ [ `Forest` ] : https://rust-lang-nursery.github.io/chalk/doc/chalk_engine/forest/struct.Forest.html
51
+
50
52
### Walkthrough
51
53
52
54
Perhaps the easiest way to explain how the solver works is to walk
53
55
through an example. Let's imagine that we have the following program:
54
56
55
- ``` rust
57
+ ``` rust,ignore
56
58
trait Debug { }
57
59
58
60
struct u32 { }
@@ -65,19 +67,22 @@ struct Vec<T> { }
65
67
impl<T: Debug> Debug for Vec<T> { }
66
68
```
67
69
68
- Now imagine that we want to find answers for the query `exists<T > {
69
- Rc<T >: Debug }`. The first step would be to u-canonicalize this query; this
70
- is the act of giving canonical names to all the unbound inference variables based on the
71
- order of their left-most appearance, as well as canonicalizing the universes of any
72
- universally bound names (e.g., the ` T ` in ` forall<T> { ... } ` ). In this case, there are no
73
- universally bound names, but the canonical form Q of the query might look something like:
70
+ Now imagine that we want to find answers for the query `exists<T > { Rc<T >:
71
+ Debug }`. The first step would be to u-canonicalize this query; this is the
72
+ act of giving canonical names to all the unbound inference variables based on
73
+ the order of their left-most appearance, as well as canonicalizing the
74
+ universes of any universally bound names (e.g., the ` T ` in `forall<T > { ...
75
+ }`). In this case, there are no universally bound names, but the canonical
76
+ form Q of the query might look something like:
77
+
78
+ ``` text
79
+ Rc<?0>: Debug
80
+ ```
74
81
75
- Rc<?0>: Debug
76
-
77
82
where ` ?0 ` is a variable in the root universe U0. We would then go and
78
- look for a table with this as the key: since the forest is empty, this
79
- lookup will fail, and we will create a new table T0, corresponding to
80
- the u-canonical goal Q.
83
+ look for a table with this canonical query as the key: since the forest is
84
+ empty, this lookup will fail, and we will create a new table T0,
85
+ corresponding to the u-canonical goal Q.
81
86
82
87
** Ignoring negative reasoning and regions.** To start, we'll ignore
83
88
the possibility of negative goals like ` not { Foo } ` . We'll phase them
@@ -93,7 +98,7 @@ where-clauses that are in scope. In the case of our example, there
93
98
would be three clauses, each coming from the program. Using a
94
99
Prolog-like notation, these look like:
95
100
96
- ```
101
+ ``` text
97
102
(u32: Debug).
98
103
(Rc<T>: Debug) :- (T: Debug).
99
104
(Vec<T>: Debug) :- (T: Debug).
@@ -105,9 +110,9 @@ clauses are inapplicable because `u32` and `Vec<?0>` cannot be unified
105
110
with ` Rc<?0> ` . The second clause, however, will work.
106
111
107
112
** What is a strand?** Let's talk a bit more about what a strand * is* . In the code, a strand
108
- is the combination of an inference table, an X-clause , and (possibly)
113
+ is the combination of an inference table, an _ X-clause _ , and (possibly)
109
114
a selected subgoal from that X-clause. But what is an X-clause
110
- (` ExClause ` , in the code)? An X-clause pulls together a few things:
115
+ ([ ` ExClause ` ] , in the code)? An X-clause pulls together a few things:
111
116
112
117
- The current state of the goal we are trying to prove;
113
118
- A set of subgoals that have yet to be proven;
@@ -118,7 +123,9 @@ The general form of an X-clause is written much like a Prolog clause,
118
123
but with somewhat different semantics. Since we're ignoring delayed
119
124
literals and region constraints, an X-clause just looks like this:
120
125
121
- G :- L
126
+ ``` text
127
+ G :- L
128
+ ```
122
129
123
130
where G is a goal and L is a set of subgoals that must be proven.
124
131
(The L stands for * literal* -- when we address negative reasoning, a
@@ -128,7 +135,9 @@ that if we are able to prove L then the goal G can be considered true.
128
135
In the case of our example, we would wind up creating one strand, with
129
136
an X-clause like so:
130
137
131
- (Rc<?T>: Debug) :- (?T: Debug)
138
+ ``` text
139
+ (Rc<?T>: Debug) :- (?T: Debug)
140
+ ```
132
141
133
142
Here, the ` ?T ` refers to one of the inference variables created in the
134
143
inference table that accompanies the strand. (I'll use named variables
@@ -141,37 +150,45 @@ is the subgoal after the turnstile (`:-`) that we are currently trying
141
150
to prove in this strand. Initally, when a strand is first created,
142
151
there is no selected subgoal.
143
152
144
- ** Activating a strand.** Now that we have created the table T0 and
145
- initialized it with strands, we have to actually try and produce an
146
- answer. We do this by invoking the ` ensure_answer ` operation on the
147
- table: specifically, we say ` ensure_answer(T0, A0) ` , meaning "ensure
148
- that there is a 0th answer".
149
-
150
- Remember that tables store not only strands, but also a vector of
151
- cached answers. The first thing that ` ensure_answer ` does is to check
152
- whether answer 0 is in this vector. If so, we can just return
153
- immediately. In this case, the vector will be empty, and hence that
154
- does not apply (this becomes important for cyclic checks later on).
153
+ [ `ExClause` ] : https://rust-lang-nursery.github.io/chalk/doc/chalk_engine/struct.ExClause.html
155
154
156
- When there is no cached answer, ` ensure_answer ` will try to produce
157
- one. It does this by selecting a strand from the set of active
158
- strands -- the strands are stored in a ` VecDeque ` and hence processed
159
- in a round-robin fashion. Right now, we have only one strand, storing
160
- the following X-clause with no selected subgoal:
161
-
162
- (Rc<?T>: Debug) :- (?T: Debug)
155
+ ** Activating a strand.** Now that we have created the table T0 and
156
+ initialized it with strands, we have to actually try and produce an answer.
157
+ We do this by invoking the [ ` ensure_root_answer ` ] operation on the table:
158
+ specifically, we say ` ensure_root_answer(T0, A0) ` , meaning "ensure that there
159
+ is a 0th answer A0 to query T0".
160
+
161
+ Remember that tables store not only strands, but also a vector of cached
162
+ answers. The first thing that [ ` ensure_root_answer ` ] does is to check whether
163
+ answer A0 is in this vector. If so, we can just return immediately. In this
164
+ case, the vector will be empty, and hence that does not apply (this becomes
165
+ important for cyclic checks later on).
166
+
167
+ When there is no cached answer, [ ` ensure_root_answer ` ] will try to produce one.
168
+ It does this by selecting a strand from the set of active strands -- the
169
+ strands are stored in a ` VecDeque ` and hence processed in a round-robin
170
+ fashion. Right now, we have only one strand, storing the following X-clause
171
+ with no selected subgoal:
172
+
173
+ ``` text
174
+ (Rc<?T>: Debug) :- (?T: Debug)
175
+ ```
163
176
164
177
When we activate the strand, we see that we have no selected subgoal,
165
178
and so we first pick one of the subgoals to process. Here, there is only
166
179
one (` ?T: Debug ` ), so that becomes the selected subgoal, changing
167
180
the state of the strand to:
168
181
169
- (Rc<?T>: Debug) :- selected(?T: Debug, A0)
182
+ ``` text
183
+ (Rc<?T>: Debug) :- selected(?T: Debug, A0)
184
+ ```
170
185
171
186
Here, we write ` selected(L, An) ` to indicate that (a) the literal ` L `
172
187
is the selected subgoal and (b) which answer ` An ` we are looking for. We
173
188
start out looking for ` A0 ` .
174
189
190
+ [ `ensure_root_answer` ] : https://rust-lang-nursery.github.io/chalk/doc/chalk_engine/forest/struct.Forest.html#method.ensure_root_answer
191
+
175
192
** Processing the selected subgoal.** Next, we have to try and find an
176
193
answer to this selected goal. To do that, we will u-canonicalize it
177
194
and try to find an associated table. In this case, the u-canonical
@@ -189,7 +206,7 @@ will be:
189
206
We can thus summarize the state of the whole forest at this point as
190
207
follows:
191
208
192
- ```
209
+ ``` text
193
210
Table T0 [Rc<?0>: Debug]
194
211
Strands:
195
212
(Rc<?T>: Debug) :- selected(?T: Debug, A0)
@@ -215,7 +232,7 @@ answer). The strand is then removed from the list of strands.
215
232
216
233
The state of table T1 is therefore:
217
234
218
- ```
235
+ ``` text
219
236
Table T1 [?0: Debug]
220
237
Answers:
221
238
A0 = [?0 = u32]
@@ -227,16 +244,18 @@ Table T1 [?0: Debug]
227
244
Note that I am writing out the answer A0 as a substitution that can be
228
245
applied to the table goal; actually, in the code, the goals for each
229
246
X-clause are also represented as substitutions, but in this exposition
230
- I've chosen to write them as full goals, following NFTD.
231
-
247
+ I've chosen to write them as full goals, following [ NFTD] .
248
+
249
+ [ NFTD ] : ./bibliography.html#slg
250
+
232
251
Since we now have an answer, ` ensure_answer(T1, A0) ` will return ` Ok `
233
252
to the table T0, indicating that answer A0 is available. T0 now has
234
253
the job of incorporating that result into its active strand. It does
235
254
this in two ways. First, it creates a new strand that is looking for
236
255
the next possible answer of T1. Next, it incorpoates the answer from
237
256
A0 and removes the subgoal. The resulting state of table T0 is:
238
257
239
- ```
258
+ ``` text
240
259
Table T0 [Rc<?0>: Debug]
241
260
Strands:
242
261
(Rc<?T>: Debug) :- selected(?T: Debug, A1)
@@ -250,7 +269,7 @@ then be returned up to our caller, and the whole forest goes quiescent
250
269
at this point (remember, we only do enough work to generate * one*
251
270
answer). The ending state of the forest at this point will be:
252
271
253
- ```
272
+ ``` text
254
273
Table T0 [Rc<?0>: Debug]
255
274
Answer:
256
275
A0 = [?0 = u32]
0 commit comments