Altering Term Arguments -- library(changearg)

The predicates in library(changearg) allow you to construct a new term that is identical to an old term except that one of its elements has been replaced or two of its elements have been swapped. Using these operations, you could use terms as one-dimensional arrays; however, though the elements of such arrays can be accessed in O(1) time using arg/3, changing an element takes O(N) time, where N is the arity of the term. See library(logarr) for a more efficient way of implementing arrays in Prolog.

Why then are these operations provided? To aid in the construction of term-rewriting systems. For example, suppose you have a set of rewrite rules expressed as a table

     rewrite_rule(X*0, 0).
     rewrite_rule(X*1, X).
     rewrite_rule(K*X, X*K) :- integer(K).
     rewrite_rule(X*(Y*Z), (X*Y)*Z).

which you want exhaustively applied to a term. You could write

     waterfall(Expr, Final) :-
             path_arg(Path, Expr, Lhs),
             rewrite_rule(Lhs, Rhs),
             change_path_arg(Path, Expr, Modified, Rhs),
             waterfall(Modified, Final).
     waterfall(Expr, Expr).


     | ?- waterfall((a*b)*(c*0)*d, X).
     X = 0
     | ?- waterfall((1*a)*(2*b), X).
     X = a*2*b

The predicates supplied by library(changearg) are as follows:

change_arg(+Index, ?OldTerm, ?OldArg, ?NewTerm, ?NewArg)
is true when OldTerm and NewTerm are identical except that the Indexth argument of OldTerm is OldArg and the Indexth argument of NewTerm is NewArg. Either OldTerm or NewTerm should be supplied; the other term can then be found. change_arg/5 is actually quite symmetric:
          change_arg(K, O, X, N, Y)


          change_arg(K, N, Y, O, X)

have exactly the same effect. For example:

          | ?- change_arg(1, c(o,l,t), X, N, u).
          X = o,
          N = c(u,l,t)
          | ?- change_arg(1, N, u, c(o,l,t), X).
          N = c(u,l,t),
          X = o
          | ?- change_arg(3, SALE, E, s(a,l,t), T).
          SALE = s(a,l,E),
          E = _755,
          T = t
          | ?- change_arg(3, a+b, b, X, c).

change_arg(+Index, ?OldTerm, ?NewTerm, ?NewArg)
is identical to change_arg/5 except that the OldArg argument is omitted. Please note: this argument order may be surprising if you think about this predicate on its own; however, it makes sense in the context of the entire group.
like change_arg/[4,5] except that Index=0 is allowed, in which case the principal function symbol is changed. Do not use this in new programs; use change_arg/5 or change_functor/5 directly. The order in which values for Index are enumerated is not defined.
change_functor(?OldTerm, ?OldSymbol, ?NewTerm, ?NewSymbol, ?Arity)
is true when OldTerm and NewTerm are identical terms, except that the functor of OldTerm is OldSymbol/Arity, and the functor of NewTerm is NewSymbol/Arity. This is similar to same_functor/3 in some respects (lib-tma-samefunctor), such as the fact that any of the arguments can be solved for. If OldTerm and NewSymbol are instantiated, or NewTerm and OldSymbol are instantiated, or NewSymbol, OldSymbol, and Arity are instantiated, that is enough information to proceed. Note that OldSymbol or NewSymbol may be a number, in which case Arity must be 0.
swap_args(+Index1, +Index2, ?OldTerm, ?Arg1, ?NewTerm, ?Arg2)
is true when OldTerm and NewTerm are identical except that
                           at Index1       at Index2
          in OldTerm          Arg1            Arg2
          in NewTerm          Arg2            Arg1

that is, the arguments at Index1 and Index2 have been swapped. As with change_arg/5, swap_args/6 is symmetric; the following terms have exactly the same effect.

          swap_args(I, J, O, X, N, Y)
          swap_args(I, J, N, Y, O, X)

For example:

          | ?- swap_args(1, 4, f(X,e,a,Y,e,r), r, T, d).
          X = r,
          Y = d,
          T = f(d,e,a,r,e,r)

swap_args(+Index1, +Index2, ?OldTerm, ?NewTerm)
is identical to swap_args/6 except that the Arg1 and Arg2 arguments are omitted.
          | ?- swap_args(1, 4, f(r,e,a,d), X).
          X = f(d,e,a,r)

change_path_arg(+Path, ?OldTerm, ?OldSub, ?NewTerm, ?NewSub)
is true when OldTerm and NewTerm are identical terms except that
          path_arg(Path, OldTerm, OldSub),
          path_arg(Path, NewTerm, NewSub)

That is, the subterm of OldTerm at Path was OldSub and is replaced by NewSub in NewTerm, and there are no other differences between OldTerm and NewTerm. This is to change_arg/5 as path_arg/3 is to arg/3.

change_path_arg(+Path, ?OldTerm, ?NewTerm, ?NewSub)
is identical to change_path_arg/5 except that the OldSub argument is omitted.
          | ?- OldTerm = this*is+an*example,
          |    path_arg(Path, OldTerm, this),
          |    change_path_arg(Path, OldTerm, NewTerm, it).
          OldTerm = this*is+an*example,
          Path = [1,1],
          NewTerm = it*is+an*example