Read_term_from_atom requires the option variable_names to be sorted in order of use

While using read_term_from_atom, to construct a string into a callable format. It has a quirk where the order of the variables in either the replacement list or their first use is significant. Below 1,2,4,5 work (s), but 3,6 don’t (f).

1. s Repl=['A'=1.05,'B'=0.24], Using='(1+A)*B', read_term_from_atom(Using, TT, [variable_names(Repl)]).
2. s Repl=['A'=1.05,'B'=0.24], Using='(A+A)*B', read_term_from_atom(Using, TT, [variable_names(Repl)]).
3. f Repl=['A'=1.05,'B'=0.24], Using='B*(A+A)', read_term_from_atom(Using, TT, [variable_names(Repl)]).
4. s Repl=['A'=1.05,'B'=0.24], Using='(A*B)+A', read_term_from_atom(Using, TT, [variable_names(Repl)]).
5. s Repl=['A'=1.05,'B'=0.24, 'C'=3], Using='(A*B)+A+B+C', read_term_from_atom(Using, TT, [variable_names(Repl)]).
6. f Repl=['A'=1.05,'B'=0.24, 'C'=3], Using='(A*C)+A+B', read_term_from_atom(Using, TT, [variable_names(Repl)]).

A trace of 2 & 3. for reference.

?- Repl=['A'=1.05,'B'=0.24], Using='(A+A)*B', trace,read_term_from_atom(Using, TT, [variable_names(Repl)]).
   Call: (11) read_term_from_atom('(A+A)*B', _55030, [variable_names(['A'=1.05, 'B'=0.24])]) ? creep
   Exit: (11) read_term_from_atom('(A+A)*B', (1.05+1.05)*0.24, [variable_names(['A'=1.05, 'B'=0.24])]) ? creep
Repl = ['A'=1.05, 'B'=0.24],
Using = '(A+A)*B',
TT = (1.05+1.05)*0.24.

?- Repl=['A'=1.05,'B'=0.24], Using='B*(A+A)', trace, read_term_from_atom(Using, TT, [variable_names(Repl)]).
   Call: (11) read_term_from_atom('B*(A+A)', _66838, [variable_names(['A'=1.05, 'B'=0.24])]) ? creep
   Fail: (11) read_term_from_atom('B*(A+A)', _66838, [variable_names(['A'=1.05, 'B'=0.24])]) ? creep
false.

Performed on HardenedBSD-13.2s and FreeBSD-14.0p5 using swi-pl 9.0.4 and FreeBSD12.4S with swipl 8.4.3.

FYI: I’m using yaml to provide instructions to manage my servers, and noticed this anomoly when I didn’t pay attention to the order of the variables. Perhaps a flag is required for repeated searches of the subatoms to be replaced, which is likely to reduce performance? This is a fantastic and incredibly useful predicate - which has a little quirk at the moment. :slightly_smiling_face:

read/2 using the variable_names(-Vars) option should be called with Vars unbound. The order of the variables is not defined, though for current SWI-Prolog is the lexical order AFAIK. The list [a,b] simply does not unify with [b,a]. The simplest and most efficient way to deal with this is to sort both Repl and the list produced by read_term_from_atom/3 and then unify them. That should work, provided Repl is bound to a proper list and all variable names are already instantiated and (thus) the order is determined.