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
orsetof/3
where you normally have to specify the the variables in whose binding you are not interested using theVar^Goal
construct (markingVar
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].