Prolog arithmetic in foreach

2019-07-27 22:24发布

问题:

So I'm learning Prolog. One of the things I've found to be really obnoxious is demonstrated in the following example:

foreach(
  between(1,10,X),
  somePredicate(X,X+Y,Result)
).

This does not work. I am well aware that X+Y is not evaluated, here, and instead I'd have to do:

foreach(
  between(1,10,X),
  (
    XPlusY is X + Y,
    somePredicate(X, XPlusY, Result)
  )
).

Except, that doesn't work, either. As near as I can tell, the scope of XPlusY extends outside of foreach - i.e., XPlusY is 1 + Y, XPlusY is 2 + Y, etc. must all be true AT ONCE, and there is no XPlusY for which that is the case. So I have to do the following:

innerCode(X, Result) :-
  XPlusY is X + Y,
  somePredicate(X, XPlusY, Result).
...

foreach(
  between(1,10,X),
  innerCode(X, Result)
).

This, finally, works. (At least, I think so. I haven't tried this exact code, but this was the path I took earlier from "not working" to "working".) That's fine and all, except that it's exceptionally obnoxious. If I had a way of evaluating arithmetic operations in-line, I could halve the lines of code, make it more readable, and NOT create a one-use clutter predicate.

Question: Is there a way to evaluate arithmetic operations in-line, without declaring a new variable?

Failing that, it would be acceptable (and, in some cases, still useful for other things) if there were a way to restrict the scope of new variables. Suppose, for instance, you could define a block within the foreach, where the variables visible from the outside were marked, and any other variables in the block were considered new for that execution of the block. (I realize my terminology may be incorrect, but hopefully it gets the point across.) For example, something resembling:

foreach(
  between(1,10,X),
  (X, Result){
    XPlusY is X + Y,
    somePredicate(X, XPlusY, Result)
  }
).

A possible solution might be if we can declare a lambda in-line, and immediately call it. Summed up:

Alternate question: Is there a way to limit the scope of new variables within a predicate, while retaining the ability to perform lasting unifications on one or more existing variables?

(The second half I added as clarification in response to an answer about forall.)

A solution to both questions is preferred, but a solution to either will suffice.

回答1:

library(yall) allows you to define lambda expressions. For instance

?- foreach(between(1,3,X),call([Y]>>(Z is Y+1,writeln(Z)),X)).
2
3
4
true.

Alternatively, library(lambda) provides the construct:

?- [library(lambda)].
true.

?- foreach(between(1,3,X),call(\Y^(Z is Y+1,writeln(Z)),X)).
2
3
4
true.

In SWI-Prolog, library(yall) is autoloaded, while to get library(lambda) you should install the related pack:

?- pack_install(lambda).


回答2:

Use in alternative the forall/2 de facto standard predicate:

forall(
  between(1,10,X),
  somePredicate(X,X+Y,Result)
).

While the foreach/2 predicate is usually implemented in the way you describe, the forall/2 predicate is defined as:

% forall(@callable, @callable)

forall(Generate, Test) :-
    \+ (Generate, \+ Test).

Note that the use of negation implies that no bindings will be returned when a call to the predicate succeeds.

Update

Lambda libraries allow the specification of both lambda global (aka lambda free) and lambda local variables (aka lambda parameters). Using Logtalk lambdas syntax (available also in SWI-Prolog in library(yall), you can write (reusing Carlo's example) e.g.

?- G = 2, foreach(between(1,3,X),call({G}/[Y]>>(Z is Y+G,writeln(Z)),X)).
3
4
5
G = 2.

?- G = 4, foreach(between(1,3,X),call({G}/[Y]>>(Z is Y+G,writeln(Z)),X)).
5
6
7
G = 4.

Thus, it's possible to use lambdas to limit the scope of some variables within a goal without also limiting the scope of every unification in the goal.