Does prolog have a spread/splat/*args operator?

603 views Asked by At

In many procedural languages (such as python), I can "unpack" a list and use it as arguments for a function. For example...

def print_sum(a, b, c):
  sum = a + b + c
  print("The sum is %d" % sum)

print_sum(*[5, 2, 1])

This code will print: "The sum is 8"

Here is the documentation for this language feature.

Does prolog have a similar feature?

Is there a way to replicate this argument-unpacking behaviour in Prolog?

For example, I'd like to unpack a list variable before passing it into call.

Could I write a predicate like this?

assert_true(Predicate, with_args([Input])) :-
  call(Predicate, Input).

% Where `Input` is somehow unpacked before being passed into `call`.

...That I could then query with

?- assert_true(reverse, with_args([ [1, 2, 3], [3, 2, 1] ])).
% Should be true, currently fails.

?- assert_true(succ, with_args([ 2, 3 ]).
% Should be true, currently fails.

?- assert_true(succ, with_args([ 2, 4 ]).
% Should be false, currently fails.

Notes

  • You may think that this is an XY Problem. It could be, but don't get discouraged. It'd be ideal to receive an answer for just my question title.

  • You may tell me that I'm approaching the problem poorly. I know your intentions are good, but this kind of advice won't help to answer the question. Please refer to the above point.

  • Perhaps I'm approaching Prolog in too much of a procedural mindset. If this is the case, then what mindset would help me to solve the problem?

  • I'm using SWI-Prolog.

2

There are 2 answers

0
AudioBubble On BEST ANSWER

First: it is too easy, using unification and pattern matching, to get the elements of a list or the arguments of any term, if you know its shape. In other words:

sum_of_three(X, Y, Z, Sum) :- Sum is X+Y+Z.

?- List = [5, 2, 1],
   List = [X, Y, Z], % or List = [X, Y, Z|_] if the list might be longer
   sum_of_three(X, Y, Z, Sum).

For example, if you have command line arguments, and you are interested only in the first two command line arguments, it is easy to get them like this:

current_prolog_flag(argv, [First, Second|_])

Many standard predicates take lists as arguments. For example, any predicate that needs a number of options, as open/3 and open/4. Such a pair could be implemented as follows:

open(SrcDest, Mode, Stream) :-
    open(SrcDest, Mode, Stream, []).

open(SrcDest, Mode, Stream, Options) :-
    % get the relevant options and open the file

Here getting the relevant options can be done with a library like library(option), which can be used for example like this:

?- option(bar(X), [foo(1), bar(2), baz(3)]).
X = 2.

So this is how you can pass named arguments.

Another thing that was not mentioned in the answer by @false: in Prolog, you can do things like this:

Goal = reverse(X, Y), X = [a,b,c], Y = [c,b,a]

and at some later point:

call(Goal)

or even

Goal

To put it differently, I don't see the point in passing the arguments as a list, instead of just passing the goal as a term. At what point are the arguments a list, and why are they packed into a list?

To put it differently: given how call works, there is usually no need for unpacking a list [X, Y, Z] to a conjunction X, Y, Z that you can then use as an argument list. As in the comment to your question, these are all fine:

call(reverse, [a,b,c], [c,b,a])

and

call(reverse([a,b,c]), [c,b,a])

and

call(reverse([a,b,c], [c,b,a]))

The last one is the same as

Goal = reverse([a,b,c], [c,b,a]), Goal

This is why you can do something like this:

?- maplist(=(X), [Y, Z]).
X = Y, Y = Z.

instead of writing:

?- maplist(=, [X,X], [Y, Z]).
X = Y, Y = Z.
5
false On

The built-in (=..)/2 (univ) serves this purpose. E.g.

?- G =.. [g, 1, 2, 3].
   G = g(1,2,3).

?- g(1,2,3) =.. Xs.
   Xs = [g,1,2,3].

However, note that many uses of (=..)/2 where the number of arguments is fixed can be replaced by call/2...call/8.