3

I want to generate 3 random numbers using what could be called "iteration & collection" predicates.

I'm collecting comments on any of the attempts below.

Writing a predicate inline is confusing and error-prone (or maybe even impossible with the is in there?), so we define:

side_effect_and_unify(X) :- 
   format("Called with ~w\n",X),
   X is random(100),
   format("Returning with ~w\n",X).

Using forall/2

We read:

The predicate forall/2 (forall(:Cond,:Action)) is implemented as \+ ( Cond, \+ Action), i.e., There is no instantiation of Cond for which Action is false..

This should "shield off" any variable instantiations whatsover. Not surprisingly:

?- length(L,3),forall(member(X,L),side_effect_and_unify(X)).
Called with _32964
Returning with 94
Called with _32964
Returning with 36
Called with _32964
Returning with 93
L = [_33490, _33496, _33502].

100% sure it's not possible to get those random numbers into L.

Using foreach/2

We read:

Unlike forall/2, which runs a failure-driven loop that proves Goal for each solution of Generator, foreach/2 creates a conjunction. Each member of the conjunction is a copy of Goal, where the variables it shares with Generator are filled with the values from the corresponding solution.

?- length(L,3),foreach(member(X,L),side_effect_and_unify(X)).
Called with _29916
Returning with 33
Called with _29916
Returning with 3
Called with _29916
Returning with 97
L = [_30434, _30440, _30446].

No joy, but is it possible?

The description makes me think this is what runs:

?- L=[L1,L2,L3],side_effect_and_unify(L1),side_effect_and_unify(L2),side_effect_and_unify(L3).
Called with _31202
Returning with 88
Called with _31208
Returning with 81
Called with _31214
Returning with 5
L = [88, 81, 5],
L1 = 88,
L2 = 81,
L3 = 5.

but apparently not.

Using findall/3

?- length(L,3),findall(X,(member(X,L),side_effect_and_unify(X)),Out).
Called with _28410
Returning with 75
Called with _28410
Returning with 18
Called with _28410
Returning with 66
L = [_29046, _29052, _29058],
Out = [75, 18, 66].

Yes!

Using bagof/3

bagof/3 is friendlier, but we need to tell it to "not bind var L in the goal". I'm not entirely certain about the meaning: Apparently it means L is a variable over the goal, so it can take on the value defined for the L outside the goal.

Although here we read:

This features shines when combined with bagof/3 or setof/3 where you normally have to specify the the variables in whose binding you are not interested using the Var^Goal construct (marking Var as existential quantified).

This would mean the opposite: L is a free variable inside the goal (but then the line below wouldn't work). What's the right way to think about this?

?- length(L,3),bagof(X,L^(member(X,L),side_effect_and_unify(X)),Out).
Called with _24336
Returning with 10
Called with _24336
Returning with 24
Called with _24336
Returning with 22
L = [_24912, _24918, _24924],
Out = [10, 24, 22].

Another speciality of bagof/3 based on a remark here:

[it] doesn't copies terms it accumulates in list. This can be fundamental, for instance, when your list will collect attributed variables, like to ones you use when modelling a problem with library(clpfd).

Using maplist/2

Ok, that's easy.

?- length(L,3),maplist(side_effect_and_unify,L).
Called with _26224
Returning with 23
Called with _26230
Returning with 63
Called with _26236
Returning with 70
L = [23, 63, 70].

Not only nicer, but IMHO, actually viably readable with yall. You actually see the metacall:

?- length(L,3),maplist([X]>>side_effect_and_unify(X),L).
Called with _27420
Returning with 34
Called with _27426
Returning with 41
Called with _27432
Returning with 25
L = [34, 41, 25].
David Tonhofer
  • 14,559
  • 5
  • 55
  • 51

0 Answers0