Limiting variable scope in prolog

2k views Asked by At

I want to declare a list of lists, like so:

%% example 1
Xs = [
  [[A],[[A]]], 
  [[A],[[A],[A]]], 
  [[A],[[A],[A],[A]]]
].

Here, the symbol A refers to the same variable in each list. Executing maplist(writeln,xs) results in the following output:

[[_G1],[[_G1]]]
[[_G1],[[_G1],[_G1]]]
[[_G1],[[_G1],[_G1],[_G1]]]

I want to use the same symbol A in each list, but for the variable to be distinct for each list, to give the following output:

[[_G1],[[_G1]]]
[[_G2],[[_G2],[_G2]]]
[[_G3],[[_G3],[_G3],[_G3]]]

The only way I make this work is give each list its own unique variable, like so:

%% example 2
Xs = [
  [[A1],[[A1]]], 
  [[A2],[[A2],[A2]]], 
  [[A3],[[A3],[A3],[A3]]]
].

Is there any Prolog syntax, so that there is no need to number each variable, as per example 2? I tried adding brackets around the lists like so:

Xs = [
  ([[A],[[A]]]), 
  ([[A],[[A],[A]]]), 
  ([[A],[[A],[A],[A]]])
].

But this gives me the same output as example 1.

3

There are 3 answers

0
false On

If you want to write out variables and specify a precise name for them, you need the write-option variable_names/1. This answer explains how. Alternatively, you might use the legacy predicate numbervars/3 which unifies distinct variables with a term '$VAR'(N), and then use either writeq/1 or the write-option numbervars(true).

But both methods will not work in the case you indicate. In fact, it was sheer luck that your query

 ?- Xs = [[[A],[A]],[[A],[A],[A]],[[A],[A],[A],[A]]], maplist(writeln,Xs).

produced the same variables for the different lists. It's even worse, the very same goal writing the very same list, may produce different variable names for different invocations:

p(N) :-
   length(_,N),
   length(K,1),
   writeq(K),
   garbage_collect,
   writeq(K).

For p(100), SICStus writes [_776][_46], SWI writes [_G517][_G3]. Brief, you caught Prolog on a good day. This is not surprising, for the standard only requires an "implementation dependent" value for a name with a few restrictions: It starts with underscore, and the remaining characters are different for different variables, and the same for the same variable within the same write goal. Here is ISO/IEC 13211-1:1995 on this:

7.10.5 Writing a term

When a term Term is output using write_term/3 (8.14.2)
the action which is taken is defined by the rules below:

a) If Term is a variable, a character sequence repre-
senting that variable is output. The sequence begins
with _ (underscore) and the remaining characters are
implementation dependent. The same character sequence
is used for each occurrence of a particular variable in
Term. A different character sequence is used for each
distinct variable in Term.

The reason for this is that a globally consistent naming of variables would produce a lot of overhead in implementations.

To summarize: If you want to use different variable names for the same variable, then use variable_names/1 with different arguments. If you want the variable to be actually different, then name them differently, or use copy_term/2 accordingly.

4
mat On

You can do it like this:

First, create the desired list structure, with different variables:

?- maplist(length, Lists, [2,3,4]).
Lists = [[X1, X2], [X3, X4, X5], [X6, X7, X8, X9]].

Then, using the following additional definition:

same_element(Ls) :- maplist(=([_]), Ls).

you can unify variables that are in the same sublist to the same term:

?- maplist(same_element, [[X1, X2], [X3, X4, X5], [X6, X7, X8, X9]]).
X1 = X2, X2 = [_G1141],
X3 = X4, X4 = X5, X5 = [_G1149],
X6 = X7, X7 = X8, X8 = X9, X9 = [_G1157].

In combination:

?- maplist(length, Lists, [2,3,4]),
   maplist(same_element, Lists),
   maplist(writeln, Lists).

yielding:

[[_G1079],[_G1079]]
[[_G1087],[_G1087],[_G1087]]
[[_G1095],[_G1095],[_G1095],[_G1095]]

Now, with the following Emacs definitions:

(defun nice-variables (start end)
  (interactive "r")
  (goto-char start)
  (let ((n 1)
        (variables nil)
        (m (make-marker)))
    (set-marker m end)
    (while (and (<= (point) (marker-position m))
                (re-search-forward "_G" (marker-position m) t))
      (let* ((from (point))
             (len (skip-chars-forward "0-9"))
             (str (buffer-substring-no-properties from (+ from len)))
             (num (assoc str variables)))
        (delete-backward-char (+ len 2))
        (if num
            (insert (format "X%d" (cdr num)))
          (setq variables (cons (cons str n) variables))
          (insert (format "X%d" n))
          (setq n (1+ n)))))))

and M-x nice-variables RET on the region, you get:

[[X1],[X1]]
[[X2],[X2],[X2]]
[[X3],[X3],[X3],[X3]]

This is also what I used on the output of the first query above, to make it more readable.

Thus, you can either generate the structure you want dynamically, by unifying variables you want to be the same, or copy & paste the output above and use it with slight modifications in your program directly.

0
AudioBubble On

Variable names in Prolog have a scope that spans a single clause in a predicate definition, or a query at the top-level. So, this:

?- List = [A, A, A].

means a list with three times the same variable, A. If you put it into a predicate, it would be, say in a file my_lists.pl (I have not nested the lists like you, just to keep it simple):

my_list([A, A]).
my_list([A, A, A]).
my_list([A, A, A, A]).

The As in the three clauses are now not in the same lexical scope, so if you consult this predicate and then collect all possible instantiations of my_list(L) using for example findall/3, you get what you are after:

?- [my_lists].
true.

?- findall(L, my_list(L), Ls).
Ls = [[_G1945, _G1945],
      [_G1933, _G1933, _G1933],
      [_G1918, _G1918, _G1918, _G1918]].

Is this close to what you are looking for? What is it that you are trying to achieve?