6

I'm trying to write an SWI-Prolog predicate that applies numbervars/3 to a term's anonymous variables but preserves the user-supplied names of its non-anonymous variables. I eventually plan on adding some kind of hook to term_expansion (or something like that).

Example of desired output:

    ?- TestList=[X,Y,Z,_,_].
       > TestList=[X,Y,Z,A,B].

This answer to the question Converting Terms to Atoms preserving variable names in YAP prolog shows how to use read_term to obtain as atoms the names of the variables used in a term. This list (in the form [X='X',Y='Y',...]) does not contain the anonymous variables, unlike the variable list obtained by term_variables, making isolation of the anonymous variables fairly straightforward.

However, the usefulness of this great feature is somewhat limited if it can only be applied to terms read directly from the terminal. I noticed that all of the examples in the answer involve direct user input of the term. Is it possible to get (as atoms) the variable names for terms that are not obtained through direct user input? That is, is there some way to 'write' a term (preserving variable names) to some invisible stream and then 'read' it as if it were input from the terminal?

Alternatively... Perhaps this is more of a LaTeX-ish line of thinking, but is there some way to "wrap" variables inside single quotes (thereby atom-ifying them) before Prolog expands/tries to unify them as variables, with the end result that they're treated as atoms that start with uppercase letters rather than as variables?

ksoo
  • 434
  • 1
  • 3
  • 15
  • `write_term` is not good enough? – false Jan 24 '19 at 22:35
  • You can use `read_term/3` supplying a stream in the first position if you want to read terms from somewhere besides the terminal. Can you say more about _why_ you need this functionality? It may be that the real problem you're trying to solve can be approached more directly from another angle, if we know more about what you're trying to do. – Daniel Lyons Jan 24 '19 at 23:39
  • Oops, I linked to the wrong question and answer. I just corrected it. Sorry if that caused any confusion – ksoo Jan 27 '19 at 00:07

2 Answers2

3

You can use the ISO core standard variable_names/1 read and write option. Here is some example code, that replaces anonymous variables in a variable name mapping:

% replace_anon(+Map, +Map, -Map)
replace_anon([_=V|M], S, ['_'=V|N]) :- member(_=W, S), W==V, !, 
   replace_anon(M, S, N).
replace_anon([A=V|M], S, [A=V|N]) :- 
   replace_anon(M, S, N).
replace_anon([], _, []). 

variable_names/1 is ISO core standard. It was always a read option. It then became a write option as well. See also: https://www.complang.tuwien.ac.at/ulrich/iso-prolog/WDCor3

Here is an example run:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.25)

?- read_term(X,[variable_names(M),singletons(S)]), 
   replace_anon(M,S,N), 
   write_term(X,[variable_names(N)]).
|: p(X,Y,X).
p(X,_,X)

To use the old numbervars/3 is not recommended, since its not compatible with attribute variables. You cannot use it for example in the presence of CLP(FD).

1

Is it possible to get (as atoms) the variable names for terms that are not obtained through direct user input?

if you want to get variable names from source files you should read them from there.

The easiest way to do so using term expansion.

Solution:

read_term_from_atom(+Atom, -Term, +Options)

Use read_term/3 to read the next term from Atom.

Atom is either an atom or a string object.

It is not required for Atom to end with a full-stop.

Use Atom as input to read_term/2 using the option variable_names and return the read term in Term and the variable bindings in variable_names(Bindings).

Bindings is a list of Name = Var couples, thus providing access to the actual variable names. See also read_term/2.

If Atom has no valid syntax, a syntax_error exception is raised.

write_term( Term ) :-
  numbervars(Term, 0, End),
  write_canonical(Term), nl.
Anton Danilov
  • 1,246
  • 11
  • 26
  • Thanks, this works perfectly when working from an atom. But what if the term I want the variable names from isn't an atom, but (for example) a compound functor, a list, or a standalone variable? These can all be handled with `read_term` and direct user input, as the answer I linked demonstrates... but is there some way to directly redirect the output of a `write_term` to `read_term`, etc, _without_ parsing the variables as variables / without replacing them with anons? I understand how to use `read_term_from_atom`, but I'm looking for a sort of `read_term_from_term` or `read_term_verbatim`... – ksoo Jan 26 '19 at 01:40
  • possibly you simply need `portray_clause/1`. This pretty-prints terms assigning variable names starting from `A`, `B`... http://www.swi-prolog.org/pldoc/man?predicate=portray_clause/2 – Anton Danilov Jan 26 '19 at 11:00