Missing something about (collect)

I’m missing something:

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        *($Plan requires $Item)
        ($Item is handled)
    (into $Plans)

Each plan requires two items, and is in progress if either of the two items is handled.

However, I’m getting the backtracking wrong here, because a plan that has both items handled accumulates twice.

(with some logging)

> (in progress plans $)              
plan: #fixing-the-truck, item: #duct-tape
plan: #fixing-the-truck, item: #gas-can
Query succeeded: (in progress plans [#fixing-the-truck #fixing-the-truck])

Usually, this resolves by adding a new rule, and thus a new choice point. Let’s see.

Yep.

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        ($Plan is in progress)
    (into $Plans)

($Plan is in progress)
    *($Plan  requires $Item)
    ($Item is handled)    

Is there another way to force a choice point?

Also, a completed plan is when both/all items required by a plan are handled. Not sure how to do that short of a collect and count.

One way is to put it in the condition of an if-statement. In this example, the code becomes less elegant, but in other cases it can be the other way around:

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        (if) *($Plan requires $Item) ($Item is handled) (then)-
            %% if you have something else to check, put it here
        (else)
            (fail)
        (endif)
    (into $Plans)

All items are handled when there is no un-handled item:

($Plan is completed)
    ~{ *($Plan requires $Item) ~($Item is handled) }

This also suggests another trick to solving the first problem: The plan is in progress if it is not completed. Double negation.

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        ~{ ~{ *($Plan requires $Item) ~($Item is handled) } }
    (into $Plans)

Still, moving the condition to a separate rule is probably the most readable approach.

I keep thinking that a nulti-query causes a choice point, but does it?

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        *($Plan requires $Item)
        ($Item is handled)
    (into $Plans)

When ($Item is handled) is false, I’d expect to backtrack to the next item in *($Plan requires $Item), but it seems (I think) to backtrack to *(plan $Plan). This has bitten me before.

It should go back and find the next required item, yes.

Choice points are created 1. when there are multiple rules matching a query, and 2. explicitly using (or). However, when a normal query succeeds, any choice points created inside it are discarded. So a normal query behaves like a multi-query, but with an extra step at the end where choice-points are discarded.

Choice-points can also be discarded by the (just) keyword and by negated rules (where the rule head starts with ~).

I’m afraid I’m still confused, because (w/ logging) I could see it AFAIK not going back to the next item required by the plan, to see if it was handled, but instead going to the next plan.

Again, this code:

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        *($Plan requires $Item)
        ($Item is handled)
    (into $Plans)

… feels right to me, but doesn’t execute correctly. I’m away from my computer where I can experiment on this, but without introducing an extra checkpoint via a new rule, it doesn’t work.

Perhaps something like:

(in progress plans $Plans)
    (collect $Plan)
        *(plan $Plan)
        (checkpoint) %% Create an explicit checkpoint
        *($Plan requires $Item)
        ($Item is handled)
    (into $Plans)

But, again, I don’t see why either a *(...) query doesn’t create a check point, or why the failure of a standard query seems to revert to an earlier check point *(plan $Plan). So I’m still confused.

In the context of the game, a plan is in progress if at least one of its items is handled, and completed if all of its items are handled. But that’s a side issue to my confusion about checkpoints and back tracking. Thanks for the help.

I tried the following:

(plan #planA)
(plan #planB)
(plan #planC)
(plan #planD)

(#planA requires #item1)
(#planA requires #item2)

(#planB requires #item3)
(#planB requires #item4)

(#planC requires #item2)
(#planC requires #item3)

(#planD requires #item1)
(#planD requires #item4)

(in progress plans $Plans)
        (trace on)
        (collect $Plan)
                *(plan $Plan)
                *($Plan requires $Item)
                ($Item is handled)
        (into $Plans)
        (trace off)

(intro) 
        (now) (#item1 is handled)
        (now) (#item4 is handled)
        (in progress plans $Plans)
        Result: $Plans

And got the following result:

Trace output
| | | FOUND (trace on) /tmp/test.dg:19
| | | QUERY *(plan $) /tmp/test.dg:21
| | | | ENTER (plan #planA) /tmp/test.dg:1
| | | FOUND (plan #planA) /tmp/test.dg:21
| | | QUERY *(#planA requires $) /tmp/test.dg:22
| | | | ENTER (#planA requires #item1) /tmp/test.dg:6
| | | FOUND (#planA requires #item1) /tmp/test.dg:22
| | | QUERY (#item1 is handled) /tmp/test.dg:23
| | | FOUND (#item1 is handled) /tmp/test.dg:23
| | | | ENTER (#planA requires #item2) /tmp/test.dg:7
| | | FOUND (#planA requires #item2) /tmp/test.dg:22
| | | QUERY (#item2 is handled) /tmp/test.dg:23
| | | | ENTER (plan #planB) /tmp/test.dg:2
| | | FOUND (plan #planB) /tmp/test.dg:21
| | | QUERY *(#planB requires $) /tmp/test.dg:22
| | | | ENTER (#planB requires #item3) /tmp/test.dg:9
| | | FOUND (#planB requires #item3) /tmp/test.dg:22
| | | QUERY (#item3 is handled) /tmp/test.dg:23
| | | | ENTER (#planB requires #item4) /tmp/test.dg:10
| | | FOUND (#planB requires #item4) /tmp/test.dg:22
| | | QUERY (#item4 is handled) /tmp/test.dg:23
| | | FOUND (#item4 is handled) /tmp/test.dg:23
| | | | ENTER (plan #planC) /tmp/test.dg:3
| | | FOUND (plan #planC) /tmp/test.dg:21
| | | QUERY *(#planC requires $) /tmp/test.dg:22
| | | | ENTER (#planC requires #item2) /tmp/test.dg:12
| | | FOUND (#planC requires #item2) /tmp/test.dg:22
| | | QUERY (#item2 is handled) /tmp/test.dg:23
| | | | ENTER (#planC requires #item3) /tmp/test.dg:13
| | | FOUND (#planC requires #item3) /tmp/test.dg:22
| | | QUERY (#item3 is handled) /tmp/test.dg:23
| | | | ENTER (plan #planD) /tmp/test.dg:4
| | | FOUND (plan #planD) /tmp/test.dg:21
| | | QUERY *(#planD requires $) /tmp/test.dg:22
| | | | ENTER (#planD requires #item1) /tmp/test.dg:15
| | | FOUND (#planD requires #item1) /tmp/test.dg:22
| | | QUERY (#item1 is handled) /tmp/test.dg:23
| | | FOUND (#item1 is handled) /tmp/test.dg:23
| | | | ENTER (#planD requires #item4) /tmp/test.dg:16
| | | FOUND (#planD requires #item4) /tmp/test.dg:22
| | | QUERY (#item4 is handled) /tmp/test.dg:23
| | | FOUND (#item4 is handled) /tmp/test.dg:23
| | | QUERY (trace off) /tmp/test.dg:25
Result: [#planA #planB #planD #planD]

As you pointed out in the first post, #planD appears twice because of the multi-query to ($ requires $).

I’d say everything behaves as expected here.

Thanks! I think I had enough going on in my full project that it confused the issue. It still would be nice to introduce some new (sugar?) syntax to control check point creation such that we match a plan only when we see the first handled item for a plan, rather than breaking it out into its own rule. Or maybe the rule really is the right way.