1

How do I create a predicate that takes another predicate and returns a derived version of it?

For example, pairwise predicates can be fairly mechanically extended to apply to lists:

all_whatever(_, []).
all_whatever(X, [Y|T]) :- 
    whatever(X, Y), 
    all_whatever(X, T).

What would be the definition of

pairwise_listwise(whatever, all_whatever).

If it's not possible/common/clunky/violates principles, what would be the alternative pattern?

false
  • 10,264
  • 13
  • 101
  • 209
vasily
  • 2,850
  • 1
  • 24
  • 40
  • 1
    If you are using SWI-Prolog are you seeking [library(yall): Lambda expressions](https://www.swi-prolog.org/pldoc/man?section=yall)? Or maybe something simpler such as [library(apply): Apply predicates on a list](https://www.swi-prolog.org/pldoc/man?section=apply)? Or maybe even more powerful such as [goal_expansion/2](https://www.swi-prolog.org/pldoc/man?predicate=goal_expansion/2) – Guy Coder Jul 21 '21 at 12:46

1 Answers1

3

There are two different ways to achieve what you want. The simplest, and likely preferred, way is to define a meta-predicate that takes any binary predicate and applies it to all elements of the list like so:

listwise(_,_,[]).
listwise(P,Y,[X|Xs]) :-
    call(P,Y,X),
    listwise(P,Y,Xs).

You can then call this as listwise(whatever, Y1, Xs1) to apply whatever to Y1 and each element of Xs1.

This is made possible thanks to the call/N meta-predicate. Note that this meta-predicate can also take partially constructed goals as first argument, so that an alternative formulation could be:

listwise(_,[]).
listwise(P,[X|Xs]) :-
    call(P,X),
    listwise(P,Xs).

Which is then called as listwise(whatever(Y1),Xs1). This version of the predicate is actually known as maplist/2 instead of listwise, at least in SWI-Prolog (in module library(apply)) and SICStus Prolog (in module library(lists)).

The second way to achieve what you want (actually closer to what you where asking for) is to actually define a new predicate all_whatever/2 using term expansion. Term expansion is a mechanism to rewrite terms when they are loaded (see e.g. for more details in SWI-Prolog: https://www.swi-prolog.org/pldoc/doc_for?object=term_expansion/2). I am showing here the SWI-Prolog version, which is by defining a clause for the term_expansion/2 predicate. This mechanism works differently in different systems or is altogether missing.

term_expansion(pairwise_listwise(PairPred,ListPred), ExpandedTerm) :-
    TerminalCall =.. [ListPred,_,[]],
    RecursiveCall =.. [ListPred,Y,[X|Xs]],
    SingleCall =.. [PairPred,Y,X],
    FinalCall =.. [ListPred,Y,Xs],
    ExpandedTerm = [TerminalCall, (RecursiveCall :- (SingleCall, FinalCall))].

In this clause, ExpandedTerm is a list defining the two clauses we want to define and all the terms in it are built from the predicate names using =... One can then define the new predicate as follows:

pairwise_listwise(whatever, all_whatever).

When this code is loaded, that clause will be expanded and replaced by two clauses defining the new predicate all_whatever. And now one can call for instance all_whatever(Y1,Xs1).

My preference goes to the first approach (conceptually simpler and works across Prolog versions) but I think it is also useful to be aware of the existence of the term expansion mechanism as well.

jnmonette
  • 1,794
  • 4
  • 7
  • 2
    Term-expansion is not "slightly different in different systems." It's either missing altogether or substantially different. – Paulo Moura Jul 21 '21 at 14:10
  • 2
    @Paulo Moura. Thanks for the correction, I have changed the wording. (I am only familiar with SICStus and it felt not too different from SWI at first sight.) – jnmonette Jul 21 '21 at 15:18
  • 1
    Defining a predicate just by its expansion is a highly problematic move. Rather first take a real definition and then consider some optimization maybe using term expansion. But in case of doubt, the original definition is still here. – false Jul 21 '21 at 15:56
  • 1
    @false Can you elaborate on how this is highly problematic? Also I am not sure to understand how your suggestion would work in this case? – jnmonette Jul 22 '21 at 08:22
  • 1
    The more you rely on term expansion alone, the more you will depend on actual instantiations and the like. Like the tiny nasty details of `do`-loops. Or the way how meta-calls/cuts are resolved in a similarly nasty manner. In your current wording of course the problems are all eliminated (for the moment), as you insist qua univ on atoms for `PairPred` and `ListPred` only. But once you get over this, you will have problems. – false Jul 22 '21 at 18:58
  • 1
    Forgot to add the most prominent example: DCG! For long, they have been haunted by "clever" term expansions. Would they have been defined as an interpreter first that is a meta program and not just an ad hoc expansion, most of these problems would have never existed. Like lack of steadfastness. – false Jul 23 '21 at 12:15
  • @false has anyone written a MI version of DCG? – Erik Kaplun Feb 16 '22 at 12:09
  • @ErikKaplun: For what purpose? DCGs now have similar glitches with *term-to-body* conversion just like the meta-call in general. But as long as there is no need for cut and the like, these obstacles can be avoided. – false Feb 16 '22 at 13:32
  • @ErikKaplun: See also chapter 6.3 Interpreters for DCGs of Prolog and Natural-Language Analysis. – false Feb 16 '22 at 14:52