Search code examples
prologprolog-setof

Prolog rule can condition to be before a date, but not after a date, why?


I have the following rules looking for facts in a database with certain dates:

preceding(ID,Date,Category,Preceding) :-
    setof([ID,D,Category,Amount], row(ID,D,Category,Amount), Preceding),
    D @< Date.

after(ID,After,Category,Rows) :-
    setof([ID,D,Category,Amount], row(ID,D,Category,Amount), Rows),
    D @> After.

preceding works just fine:

?- preceding(ID,'2020-01-01',Category,Preceding).
Preceding = [[100002, '2018-11-05', 'CEM', 500.0], [100007, '2018-11-01', 'Consulting Package', 100000.0], [100011, '2017-10-25', 'CEM', 500.0], [100012, '2017-10-15', 'CEM', 500.0], [100012, '2017-10-25', 'Tuition Package', 5543.37], [100013, '2017-10-15', 'CEM'|...], [100013, '2017-11-08'|...], [100014|...], [...|...]|...].

But after doesn't work:

?- after(ID,'2000-01-01',Category,Rows).
false.

Note that the only difference between these two rules is the @< vs. @> operator. I've tried changing the order of the operands around, and changing the order of the statements in the rule, but it doesn't work.

I've also tried reversing the logic:

after(ID,After,Category,Rows) :-
    setof([ID,D,Category,Amount], row(ID,D,Category,Amount), Rows),
    After @< D.

This doesn't work either.

I even wrote a separate rule to check whether I could make the operator work:

isafter(A,B) :- A @> B.

This works. But replacing D @> After with isafter(D,After) in my after rule doesn't work.

Why is it that making a "before" condition works, but an "after" condition doesn't? And can you make my after rule work please? :)

(I actually want to write a between function that uses both "before" and "after" conditions, but I realised that the exact problem with my between function was @>.)


Solution

  • preceding works just fine:

    Does it? You call it with variables ID and Category that do not get bound by the query. This is already an indication that it doesn't do exactly what you may have had in mind. Let's look inside.

    Using this database:

    row(100002, '2018-11-05', 'CEM', 500.0).
    row(100007, '2018-11-01', 'Consulting Package', 100000.0).
    

    let's just look at the setof goal inside your definitions:

    ?- setof([ID,D,Category,Amount], row(ID,D,Category,Amount), Preceding).
    Preceding = [[100002, '2018-11-05', 'CEM', 500.0], [100007, '2018-11-01', 'Consulting Package', 100000.0]].
    

    The Preceding list is computed as we expect it to, but what about D and the other variables? They don't get bound. We can make this even more explicit:

    ?- setof([ID,D,Category,Amount], row(ID,D,Category,Amount), Preceding), write('D: '), writeln(D).
    D: _5070
    Preceding = [[100002, '2018-11-05', 'CEM', 500.0], [100007, '2018-11-01', 'Consulting Package', 100000.0]].
    

    Something of the form _5070 is SWI-Prolog's notation for "unbound variable". So D is a variable after the setof call. Did you expect it to be bound to one of the dates in the Preceding list? Which one?

    So now we can get closer to the issue of @> vs. @<. What you are doing is comparing an atom like '2020-01-01' to a variable. @> and @< implement what is called the "standard order of terms". In this order, variables are always considered smaller than atoms:

    ?- SomeVariable @< some_atom.
    true.
    
    ?- SomeVariable @> some_atom.
    false.
    

    And this is why your @< always succeeds and your @> always fails. To fix this you need to go back and think again about what exactly you want your setof call to do.