Friday, October 31, 2025

Implementing CP Prolog - 6

Last week I presented the rule

time_in_minutes(TimeAtom, Minutes) :- atomic_list_concat(TimeAtom, ':', [HStr, MStr]), atom_number(HStr, H), atom_number(MStr, M), Minutes is H * 60 + M.

I ran out of time implementing atomic_list_concat (which is, along with atom_number, a standard Prolog predicate and not something that CoPilot invented); it wasn't as it appears above, but rather in the form atomic_list_concat ([H|T], ':', TimeAtom). I thought that I would start off this week by putting the arguments in the more logical order, but unfortunately this caused problems with the implementation of this predicate as it currently stood. So I had to work on this, finally getting the predicate to work both ways.

I thought it would be instructive to add logging to the goal resolver so that one could see what the current goal to be resolved is. This is helpful to see where the rule was getting stuck. I used some standard code of mine for doing this.

The standard predicate 'atom_number' should have been relatively simple to implement, but the initial code that CoPilot presented was insufficient. It hadn't considered the possibility of both arguments being variables, and in any case, CoPilot had neglected to resolve the arguments first before dealing with them. Eventually we got the code to work. In the goal resolver, first one must handle the current goal then if that succeeds, a recursive call must be made to resolve the remainder of the goals.

What does this mean? The initial goal is time_in_minutes (03:30, X). This gets replaced by the four clauses in the rule's body. If the goal atomic_list_concat succeeds, then a recursive call must be made with the three remaining goals, and if the next goal succeeds (atomic_number), then a recursive call must be made with the two remaining goals. This is something that I had missed in previous weeks for the comparison operators: I was assuming that these were the final goal to be solved, but in reality they could appear anywhere.

Once atom_number was working correctly, it was time to handle the arithmetic expession. Again, I had solved simple expressions such as 3 + 4, but here a recursive evaluation function was required. Fascinating code. I should point out here that depite all the sophistication of CoPilot, it made a rookie mistake here: one can't have a case statement where the value being evaluated is a string. This was easily corrected but just goes to show that the initial code is not always correct.

Finally the entire rule and goals were handled correctly, so that querying time_in_minutes (03:30, X) gave the solution X = 210. 

Before leaving this level of the interpreter, I wanted to refactor the common logic from ResolveGoals that I had been adding to the intrinsic functions that makes the recursive call to handle the remaining goals. This was partially successful: it broke down in places because the signature of AtomicListConcat, namely (args: PTermArray; var env: TEnvironment): boolean is not the same as that of AtomNumber, (atomTerm, numberTerm: PTerm; var env: TEnvironment):boolean. It was more successful with the comparison operators, although these too required helper functions.

What's next? First, I want to expand the 'flight finder' database - this requires the time_in_minutes rule to ensure that the passenger gets to the airport at least three hours before a flight is due to leave. But after that? I don't know. If I read some Prolog texts over the next week then maybe I will come across something that I would like to implement. But as I wrote a few weeks ago, I suspect that I am much more interested in learning how to write the interpreter than actually use it.



This day in blog history:

Blog #Date TitleTags
108631/10/2017Tom PaxtonPersonal
127031/10/2019Coincidence or deliberate reference?John Le Carre, Non-fiction books
154431/10/2022Extending cooking length for chickenCooking, Ninja grill

No comments: