Search code examples
prologvariadic-functionssplat

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


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.


Solution

  • 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.