4

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.

Community
  • 1
  • 1
byxor
  • 5,930
  • 4
  • 27
  • 44
  • 2
    You have probably noticed yourself that `call(foo, a, b)` is the same as `call(foo(a, b))` and `call(foo(a), b)`. This is why a call like `include(>(2), [1,2,3,4], R)` works. –  Jan 03 '17 at 08:54
  • Possible duplicate of [Prolog predicate with variable number of arguments](https://stackoverflow.com/questions/20777400/prolog-predicate-with-variable-number-of-arguments) – Anderson Green Sep 11 '18 at 19:23

2 Answers2

6

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.

false
  • 10,264
  • 13
  • 101
  • 209
  • This might be exactly what I need. I'll poke around with it and get back if I can use it. Thank you. – byxor Jan 03 '17 at 08:26
  • What about the receiving side? How would one go about that? What the legal way to write `my_first(First, *Rest):- nth1(1, Rest, First).`? – pkoch May 05 '20 at 23:33
  • @pkoch the same: `my_first( First, Term ) :- Term =.. [_Functor | Args], nth1(1, Args, First).`, then `?- my_first( First, g(a,b,c,d) ).` ==> `First = a.`. – Will Ness May 11 '20 at 11:48
  • That's not really what I'm looking for. How can you, without declaring a relation per arity, make all these calls behave the same? `my_first(First, a)`, `my_first(First, a, b)`, `my_first(First, a, b, c)`, `my_first(First, a, b, c, d)` I know this is not standard practice, but I'm interested in the language faculties, not the style. – pkoch May 12 '20 at 12:56
  • 1
    @pkoch for that you'd need to treat all those calls as data. with my definition you'd have to call `my_first( First, my_first( First, a, b, c, d, ...))`. (and FYI there's no notifications if you don't use the @, usually). if a relation is not defined, there's no relation to invoke, of whatever arity. and the only way to define a relation is to define a predicate, with the associated arguments, i.e. arity. you could define all the sub-clauses programmatically though, with `asserta` / `assertz`. – Will Ness May 12 '20 at 15:44
4

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