Search code examples
prologprolog-cut

How does pruning choice points in the code below make it more efficient (Prolog)?


In the code given below, there is the ! (cut) that prunes the choice point for efficiency. I am pretty certain that the reverse predicate and the agent_do_moves predicate are essential.

solve_task(Task,Cost):-
    agent_current_position(oscar,P),
    solve_task_a(Task,[b(0,0,P)],[],R,Cost,_NewPos),!,  % prune choice point for efficiency
    reverse(R,[_Init|Path]),
    agent_do_moves(oscar,Path).

Solution

  • The cut in above examples has the following effect:

    Ideally, it commits the search which might happen within solve_task_a/6 to the first answer found. This frees resources for finding further answers which improves space consumption.

    Scope problems

    However, at the same time, it might also hide further answers to agent_current_position/2. Of course, it does not make much sense to have further answers for this goal, but it might be an error that happens to sleep for a while, only to become active but still undiscovered in the worst possible situation.

    For this reason, it would be preferable to write instead of the cut

        ...,
        once( solve_task_a( ... ) ),
        ...
    

    This limits the scope precisely to what you want to express.

    Steadfastness problem

    But this is not the only possible source of problems. I see this variable Cost. Will it be instantiated when you call solve_task(Task, Cost) or not? I could do a lot of guessing here. But at least this variable might influence the answer Prolog will commit to. So solve_task(Task, 99) and solve_task(Task, Cost), Cost = 99 might produce different answers. In fact, the latter might even fail. Predicates that have such problems are said to lack steadfastness.

    To illustrate how steadfastness is easily lost in such a situation consider this (runnable) sketch of your (already improved) program:

    solve_task(Task,Cost):-
        % agent_current_position(oscar,P),
        once(solve_task_a(Task,[b(0,0,P)],[],R,Cost,_NewPos)),
        true.
    
    solve_task_a(_, _, _, _, 20, _).
    solve_task_a(_, _, _, _, 30, _).
    

    Now

    ?- solve_task(a, Cost).
       Cost = 20.
    ?- solve_task(a, 30).
       true.
    ?- solve_task(a, Cost), Cost = 30.
       false.
    

    There would be an easy way out of this problem by cleanly testing variable Cost, e.g. Cost >= 0 which produces an instantiation error should Cost be an uninstantiated variable. But if you want (as you indicate in your comment) to determine the cost, you need to put it like so:

    solve_task(Task,Cost):-
        % agent_current_position(oscar,P),
        once(solve_task_a(Task,[b(0,0,P)],[],R,CostX,_NewPos)),
        CostX = Cost
        true.
    

    In this manner we can be sure that Cost cannot influence the outcome of solve_task_a/6 (euh - provided there is no aliasing between Cost and Task - but let's assume that for the moment). One says also that output unifications are put behind the commit.

    Many people will tell you that such extra care is not needed, as you will never use solve_task(Task, Cost) with a given cost. That might be the case, but are you sure you will remember it? And are you sure the source code will remember it (without any dynamic check)? Such implicit assumptions easily accumulate to a degree were your mental capacities are overburdened.

    There is not always an easy way out. But quite often it is possible to stick to logical purity . In that case, you do not have to remember any such assumptions.


    In any case, I would recommend that you do not go into these parts of Prolog for the moment. Rather stick to , , and other clean, monotonic programs preserving . There is a lot to learn!