1
1
use crate :: context:: {
2
- Context , ContextOps , Floundered , InferenceTable , ResolventOps , UnificationOps ,
2
+ Context , ContextOps , Floundered , InferenceTable , ResolventOps , TruncateOps , UnificationOps ,
3
3
} ;
4
4
use crate :: fallible:: NoSolution ;
5
5
use crate :: forest:: Forest ;
@@ -9,7 +9,6 @@ use crate::table::AnswerIndex;
9
9
use crate :: {
10
10
Answer , CompleteAnswer , ExClause , FlounderedSubgoal , Literal , Minimums , TableIndex , TimeStamp ,
11
11
} ;
12
- use std:: mem;
13
12
14
13
type RootSearchResult < T > = Result < T , RootSearchFail > ;
15
14
@@ -284,7 +283,7 @@ impl<C: Context> Forest<C> {
284
283
) {
285
284
Ok ( ( ) ) => {
286
285
let Strand {
287
- infer,
286
+ infer : _ ,
288
287
ex_clause,
289
288
selected_subgoal : _,
290
289
last_pursued_time : _,
@@ -302,9 +301,6 @@ impl<C: Context> Forest<C> {
302
301
// subgoals may be eligble to be pursued again.
303
302
ex_clause. answer_time . increment ( ) ;
304
303
305
- // Apply answer abstraction.
306
- self . truncate_returned ( ex_clause, infer) ;
307
-
308
304
// Ok, we've applied the answer to this Strand.
309
305
return Ok ( ( ) ) ;
310
306
}
@@ -1040,6 +1036,28 @@ impl<C: Context> Forest<C> {
1040
1036
assert ! ( subgoals. is_empty( ) ) ;
1041
1037
assert ! ( floundered_subgoals. is_empty( ) ) ;
1042
1038
1039
+ // If the answer gets too large, mark the table as floundered.
1040
+ // This is the *most conservative* course. There are a few alternatives:
1041
+ // 1) Replace the answer with a truncated version of it. (This was done
1042
+ // previously, but turned out to be more complicated than we wanted and
1043
+ // and a source of multiple bugs.)
1044
+ // 2) Mark this *strand* as floundered. We don't currently have a mechanism
1045
+ // for this (only floundered subgoals), so implementing this is more
1046
+ // difficult because we don't want to just *remove* this strand from the
1047
+ // table, because that might make the table give `NoMoreSolutions`, which
1048
+ // is *wrong*.
1049
+ // 3) Do something fancy with delayed subgoals, effectively delayed the
1050
+ // truncated bits to a different strand (and a more "refined" answer).
1051
+ // (This one probably needs more thought, but is here for "completeness")
1052
+ //
1053
+ // Ultimately, the current decision to flounder the entire table mostly boils
1054
+ // down to "it works as we expect for the current tests". And, we likely don't
1055
+ // even *need* the added complexity just for potentially more answers.
1056
+ if infer. truncate_answer ( & subst) . is_some ( ) {
1057
+ self . tables [ table] . mark_floundered ( ) ;
1058
+ return None ;
1059
+ }
1060
+
1043
1061
let subst = infer. canonicalize_answer_subst ( subst, constraints, delayed_subgoals) ;
1044
1062
debug ! ( "answer: table={:?}, subst={:?}" , table, subst) ;
1045
1063
@@ -1160,7 +1178,7 @@ impl<C: Context> Forest<C> {
1160
1178
1161
1179
// Subgoal abstraction:
1162
1180
let ( ucanonical_subgoal, universe_map) = match subgoal {
1163
- Literal :: Positive ( subgoal) => self . abstract_positive_literal ( infer, subgoal) ,
1181
+ Literal :: Positive ( subgoal) => self . abstract_positive_literal ( infer, subgoal) ? ,
1164
1182
Literal :: Negative ( subgoal) => self . abstract_negative_literal ( infer, subgoal) ?,
1165
1183
} ;
1166
1184
@@ -1292,44 +1310,16 @@ impl<C: Context> Forest<C> {
1292
1310
/// abstraction function to yield the canonical form that will be
1293
1311
/// used to pick a table. Typically, this abstraction has no
1294
1312
/// effect, and hence we are simply returning the canonical form
1295
- /// of `subgoal`, but if the subgoal is getting too big, we may
1296
- /// truncate the goal to ensure termination.
1297
- ///
1298
- /// This technique is described in the SA paper.
1313
+ /// of `subgoal`; but if the subgoal is getting too big, we return
1314
+ /// `None`, which causes the subgoal to flounder.
1299
1315
fn abstract_positive_literal (
1300
1316
& mut self ,
1301
1317
infer : & mut dyn InferenceTable < C > ,
1302
1318
subgoal : & C :: GoalInEnvironment ,
1303
- ) -> ( C :: UCanonicalGoalInEnvironment , C :: UniverseMap ) {
1304
- // Subgoal abstraction: Rather than looking up the table for
1305
- // `selected_goal` directly, first apply the truncation
1306
- // function. This may introduce fresh variables, making the
1307
- // goal that we are looking up more general, and forcing us to
1308
- // reuse an existing table. For example, if we had a selected
1309
- // goal of
1310
- //
1311
- // // Vec<Vec<Vec<Vec<i32>>>>: Sized
1312
- //
1313
- // we might now produce a truncated goal of
1314
- //
1315
- // // Vec<Vec<?T>>: Sized
1316
- //
1317
- // Obviously, the answer we are looking for -- if it exists -- will be
1318
- // found amongst the answers of this new, truncated goal.
1319
- //
1320
- // Subtle point: Note that the **selected goal** remains
1321
- // unchanged and will be carried over into the "pending
1322
- // clause" for the positive link on the new subgoal. This
1323
- // means that if our new, truncated subgoal produces
1324
- // irrelevant answers (e.g., `Vec<Vec<u32>>: Sized`), they
1325
- // will fail to unify with our selected goal, producing no
1326
- // resolvent.
1319
+ ) -> Option < ( C :: UCanonicalGoalInEnvironment , C :: UniverseMap ) > {
1327
1320
match infer. truncate_goal ( subgoal) {
1328
- None => infer. fully_canonicalize_goal ( subgoal) ,
1329
- Some ( truncated_subgoal) => {
1330
- debug ! ( "truncated={:?}" , truncated_subgoal) ;
1331
- infer. fully_canonicalize_goal ( & truncated_subgoal)
1332
- }
1321
+ Some ( _) => None ,
1322
+ None => Some ( infer. fully_canonicalize_goal ( subgoal) ) ,
1333
1323
}
1334
1324
}
1335
1325
@@ -1384,57 +1374,6 @@ impl<C: Context> Forest<C> {
1384
1374
// affect completeness when it comes to subgoal abstraction.
1385
1375
let inverted_subgoal = infer. invert_goal ( subgoal) ?;
1386
1376
1387
- // DIVERGENCE
1388
- //
1389
- // If the negative subgoal has grown so large that we would have
1390
- // to truncate it, we currently just abort the computation
1391
- // entirely. This is not necessary -- the SA paper makes no
1392
- // such distinction, for example, and applies truncation equally
1393
- // for positive/negative literals. However, there are some complications
1394
- // that arise that I do not wish to deal with right now.
1395
- //
1396
- // Let's work through an example to show you what I
1397
- // mean. Imagine we have this (negative) selected literal;
1398
- // hence `selected_subgoal` will just be the inner part:
1399
- //
1400
- // // not { Vec<Vec<Vec<Vec<i32>>>>: Sized }
1401
- // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1402
- // // `selected_goal`
1403
- //
1404
- // (In this case, the `inverted_subgoal` would be the same,
1405
- // since there are no free universal variables.)
1406
- //
1407
- // If truncation **doesn't apply**, we would go and lookup the
1408
- // table for the selected goal (`Vec<Vec<..>>: Sized`) and see
1409
- // whether it has any answers. If it does, and they are
1410
- // definite, then this negative literal is false. We don't
1411
- // really care even how many answers there are and so forth
1412
- // (if the goal is ground, as in this case, there can be at
1413
- // most one definite answer, but if there are universals, then
1414
- // the inverted goal would have variables; even so, a single
1415
- // definite answer suffices to show that the `not { .. }` goal
1416
- // is false).
1417
- //
1418
- // Truncation muddies the water, because the table may
1419
- // generate answers that are not relevant to our original,
1420
- // untruncated literal. Suppose that we truncate the selected
1421
- // goal to:
1422
- //
1423
- // // Vec<Vec<T>>: Sized
1424
- //
1425
- // Clearly this table will have some solutions that don't
1426
- // apply to us. e.g., `Vec<Vec<u32>>: Sized` is a solution to
1427
- // this table, but that doesn't imply that `not {
1428
- // Vec<Vec<Vec<..>>>: Sized }` is false.
1429
- //
1430
- // This can be made to work -- we carry along the original
1431
- // selected goal when we establish links between tables, and
1432
- // we could use that to screen the resulting answers. (There
1433
- // are some further complications around the fact that
1434
- // selected goal may contain universally quantified free
1435
- // variables that have been inverted, as discussed in the
1436
- // prior paragraph above.) I just didn't feel like dealing
1437
- // with it yet.
1438
1377
match infer. truncate_goal ( & inverted_subgoal) {
1439
1378
Some ( _) => None ,
1440
1379
None => Some ( infer. fully_canonicalize_goal ( & inverted_subgoal) ) ,
@@ -1458,50 +1397,4 @@ impl<C: Context> Forest<C> {
1458
1397
} ) ;
1459
1398
debug ! ( "flounder_subgoal: ex_clause={:#?}" , ex_clause) ;
1460
1399
}
1461
-
1462
- /// Used whenever we process an answer (whether new or cached) on
1463
- /// a positive edge (the SLG POSITIVE RETURN operation). Truncates
1464
- /// the resolvent (or factor) if it has grown too large.
1465
- fn truncate_returned ( & self , ex_clause : & mut ExClause < C > , infer : & mut dyn InferenceTable < C > ) {
1466
- // DIVERGENCE
1467
- //
1468
- // In the original RR paper, truncation is only applied
1469
- // when the result of resolution is a new answer (i.e.,
1470
- // `ex_clause.subgoals.is_empty()`). I've chosen to be
1471
- // more aggressive here, precisely because or our extended
1472
- // semantics for unification. In particular, unification
1473
- // can insert new goals, so I fear that positive feedback
1474
- // loops could still run indefinitely in the original
1475
- // formulation. I would like to revise our unification
1476
- // mechanism to avoid that problem, in which case this could
1477
- // be tightened up to be more like the original RR paper.
1478
- //
1479
- // Still, I *believe* this more aggressive approx. should
1480
- // not interfere with any of the properties of the
1481
- // original paper. In particular, applying truncation only
1482
- // when the resolvent has no subgoals seems like it is
1483
- // aimed at giving us more times to eliminate this
1484
- // ambiguous answer.
1485
-
1486
- match infer. truncate_answer ( & ex_clause. subst ) {
1487
- // No need to truncate
1488
- None => { }
1489
-
1490
- // Resolvent got too large. Have to introduce approximation.
1491
- Some ( truncated_subst) => {
1492
- mem:: replace (
1493
- ex_clause,
1494
- ExClause {
1495
- subst : truncated_subst,
1496
- ambiguous : true ,
1497
- constraints : vec ! [ ] ,
1498
- subgoals : vec ! [ ] ,
1499
- delayed_subgoals : vec ! [ ] ,
1500
- answer_time : TimeStamp :: default ( ) ,
1501
- floundered_subgoals : vec ! [ ] ,
1502
- } ,
1503
- ) ;
1504
- }
1505
- }
1506
- }
1507
1400
}
0 commit comments