# Freeze interferes with multiple solutions

**URL:** <https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309>\
**Category:** Help!\
**Created:** [February 26, 2023, 2:49pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309 "2023-02-26T14:49:17Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [February 26, 2023, 2:49pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/1 "2023-02-26T14:49:17Z")

</div>

Using freeze/2

```prolog
test_cut(X) :-
    freeze(L, (X = 1 ; X = 2)),
    test_cut_list(L).

```

Then, these variations both have the same issue…

```prolog
test_cut_list([]) :- !.

```

and

```prolog
test_cut_list(L) :- 
    (L = [] -> true).

```

… both chop off the 2nd alternative, producing only:

```prolog
?- test_cut(X).
X = 1.

```

… whereas either of these 2 predicates…

```prolog
test_cut_list([]).

```

and

```prolog
test_cut_list(L) :- 
    (L = [] *-> true).

```

… produce the 2 expected solutions:

```prolog
?- test_cut(X).
X = 1 ;
X = 2.

```

Isn’t this a bug in freeze/2? The logic flow in `test_cut_list` seems to be strangely affecting the `X = 2` `freeze` path.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [February 26, 2023, 5:26pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/2 "2023-02-26T17:26:22Z")

</div>

> [@brebs](#):
>
> Isn’t this a bug in [freeze/2](https://www.swi-prolog.org/pldoc/doc_for?object=freeze/2)?

No (provided I do not read this wrong). Considering `test_cut_list([]) :- !.`,  
the head unification triggers the wakeup that is executed by inserting the wakeup goal at the “neck”. So, it first executes the frozen goal and then cuts. Other Prologs may do this differently.

Bottom line is that mixing cuts with coroutining (and constraints) is asking for trouble unless you know really well what is going on.

---

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [February 26, 2023, 5:37pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/3 "2023-02-26T17:37:18Z")

</div>

That’s a huge _gotcha_… and a huge disappointment.

Can’t the freeze/2 code be independent?

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [February 26, 2023, 6:00pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/4 "2023-02-26T18:00:04Z")

</div>

> [@brebs](#):
>
> That’s a huge _gotcha_… and a huge disappointment.

You’ve already given an example of how to avoid being disappointed: using `*->` instead of `->` or cut.

(I agree with @jan – this is not a bug; and mixing cut with coroutining is likely to end badly.)

---

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [February 26, 2023, 6:19pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/5 "2023-02-26T18:19:25Z")

</div>

I’m not seeing “likely to end badly” as a sensible language design goal.

How is this a desirable situation? Surely the freeze/2 goals being independent would be far better?

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [February 26, 2023, 7:43pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/6 "2023-02-26T19:43:13Z")

</div>

> [@brebs](#):
>
> I’m not seeing “likely to end badly” as a sensible language design goal.

I wish there were a way of banishing “cut” from Prolog. It’s full of pitfalls for the unwary. Richard O’Keefe has written extensively on the issues of the scope of “cut” (for example, what should `call(!)` do?).

I suppose part of the problem is in the mechanism that freeze/2 uses, namely attributed variables. It’s a quite elegant mis-use of something that was originally intended to provide “narrowing” semantics for unification – for example, allowing a variable to constrained to a set of possible values, with unification succeeding by reducing the possible values (failure if it’s the empty set; regular unification if the result is a single value). The “hook” for attributed variables allows running an arbitrary predicate when the variable is instantiated. (And I wonder what it means to use freeze/2 on the [“domain” attributed variable example](https://www.swi-prolog.org/pldoc/man?section=attvar) in the manual.) (Also, there are some subtle problems with how the attributed variable “hook” is invoked, which apparently Sicstus Prolog solves (or is it ECLiPSe?) – would the Sicstus implementation solve the freeze/cut issue, I wonder? … see Markus Trika’s [Attributed Variables in Prolog](https://www.metalevel.at/prolog/attributedvariables) , which I’ve now added to my stack of things to read)

On the other hand, @jan has mentioned (somewhere) that freeze/2 is slower than he would like, so perhaps there’s an opportunity to change the implementation of freeze/2 (and when/2) to use something other than attributed variables.

---

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [February 26, 2023, 8:27pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/7 "2023-02-26T20:27:38Z")

</div>

I suppose we should use \*-\>/2 as the default, instead of -\>/2, given the difference I highlighted above, despite it not being needed in the non-`freeze` context.

Erm… note to self: write _better_ (more deterministic) code 😂

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [February 26, 2023, 8:57pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/8 "2023-02-26T20:57:04Z")

</div>

> [@brebs](#):
>
> Erm… note to self: write _better_ (more deterministic) code 😂

One thing I’ve learned – sprinkling my code with cuts, in the hopes of removing some bugs or performance issues, does not usually improve it. 🤣

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [February 27, 2023, 8:15am UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/9 "2023-02-27T08:15:27Z")

</div>

> [@peter.ludemann](#):
>
> for example, what should `call(!)` do?

This is one of the areas ISO solved and where it did (IMO) a good job. Scoping of the cuts is well defined … for ISO Prolog. Attributed variables are not part of ISO. It is clear that unifying to them inserts a goal into the SLD resolution tree. It is not clear when exactly this happens. It is also clear that cuts can prune the alternatives of goals inserted by attributed variable wakeup, but exactly when is also undefined. I’ve implemented attributed variables with help from Bart Demoen and Tom Schrijvers and recall Bart already pointed at the lack of common practice on when exactly to do the wakeup. If now happens

- After head unification, inserting the wakeup as first goal of the body (creating a body if the clause is a fact).
- After success from calling a foreign predicate, inserting the wakeup right after the foreign goal
- After inlined unification, inserting the wakeup right after the unification.

A cut further down in the body thus cuts the choices left by the wakeup.

> [@peter.ludemann](#):
>
> Also, there are some subtle problems with how the attributed variable “hook” is invoked, which apparently Sicstus Prolog

SICStus does the wakeup differently. SWI-Prolog makes a single call after the unification that provides the attributes (from before the unification) and the term (can be another attvar) to which the variable is unified and does all of that in the state the system is in at the wakeup. SWI-Prolog unification with attvars is normal unification, where attvars are handled as normal variables, but unifying them schedules the wakeup call. SICStus has two calls, one before and one after unification and does these calls one unification after the other. So, effectively it does not execute the attvar unifications, but just schedules them. That makes some operations simpler and Markus claims it is necessary for implementing some solvers correctly. He never came with a convincing case though, although I won’t deny that in some scenarios SWI-Prolog’s approach is more complicated to program. Other systems typically implement something that share properties from the SICStus approach and the “Leuven” approach.

In general, you may combine coroutining and multiple constraints in a single computation. That is, if the subsystems nicely follow all (undocumented) guidelines. Unless the solvers know about each other, you cannot expect anything clever to happen.

I think implementing freeze/2 using attributed variables is fine. The entire attributes variable handling should be implemented with better performance. I don’t know how though. I also never did any detailed analysis on where the time is spent. The rough idea is simple: a unification adds a term to a global variable (implemented as a term reference at a well known location) using backtrackable assignments. At a wakeup moment, the system checks this global variable is nonvar and injects a call to $wakeup/1, passing the value of this global variable (after resetting the variable itself to var).

---

<div class="post-metadata">

**Author:** ![ridgeworks](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/ridgeworks/32/886_2.png) [@ridgeworks](https://swi-prolog.discourse.group/u/ridgeworks)\
**Post date:** [February 28, 2023, 9:52pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/10 "2023-02-28T21:52:30Z")

</div>

> [@peter.ludemann](#):
>
> I wish there were a way of banishing “cut” from Prolog.

Maybe just my perspective but I have to disagree. Prolog is a nondeterministic langauage - it’s based on logic and “or” is an “any of” choice, not a “one of”. Cuts provide an efficient way of turning an uncommitted choice to a committed one, i.e. pruning the search tree. There are committed choice languages around (Erlang is one IIRC) if that’s your preference.

> [@jan](#):
>
> This is one of the areas ISO solved and where it did (IMO) a good job. Scoping of the cuts is well defined … for ISO Prolog.

I think the main issue with the ISO cut is that it’s context sensitive; that’s why `call(!)` is semantically different than `!`. But that’s not something you normally want to do unless you’re looking at meta-interpretation. Pre-ISO days, I used a Prolog that had ancestor cuts which were not context sensitive, so `call(cut(some_pred_name)` did work as you would expect. But I can’t say as I really miss that feature very much. (There was also a clause level (local) cut, but that can be easily done using “if-then”.)

Co-routines/constraints raise the possibility that any unification can turn into the insertion/execution of arbitrary code. I agree that mixing that with cuts is a potential disaster which can best be avoided by ensuring that “frozen” goals (including any `attr_unify_hook`’s) are deterministic. It also makes the code much easier to understand IMO (no backtracking into un-frozen goals totally disconnected with the predicate being executed.) Now maybe that’s something that could be enforced by the system, i.e., `attr_unify_hook` is always called with a `once` wrapper, somewhat like the “if-then” condition.

> [@peter.ludemann](#):
>
> One thing I’ve learned – sprinkling my code with cuts, in the hopes of removing some bugs or performance issues, does not usually improve it.

Now not “sprinkling my code with cuts” _is_ good advice. But banning cuts isn’t the answer (besides being impractical at this point in history). Cuts are easy to misuse (takes just two characters) so use them only when the reason they’re necessary is understood and they have the desired (and only the desired) effect. That’s not always easy but it’s better to do it while writing the code rather than later in the game.

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [March 1, 2023, 5:52am UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/11 "2023-03-01T05:52:07Z")

</div>

> [@ridgeworks](#):
>
> Prolog is a nondeterministic langauage - it’s based on logic and “or” is an “any of” choice, not a “one of”. Cuts provide an efficient way of turning an uncommitted choice to a committed one, i.e. pruning the search tree. There are committed choice languages around (Erlang is one IIRC) if that’s your preference.

Cuts to force determinism are OK – although I wish the compiler was smart enough to figure that out.  
If-then-else (especially the `*->` kind) is also OK, if it’s a shorthand for listing out all the possibilities (e.g., the Picat-style of matching).

But I’m having trouble with the following, which doesn’t seem to have a straightforward transformation to if-than-else that doesn’t also leave a choicepoint (code by @anon95304481).

```prolog
runs([X-N|R]) --> [X], {var(N)}, !, run(X, M), {N is M+1}, runs(R).
runs([X-N|R]) --> [X], {N > 0}, !, {M is N-1}, run(X, M), runs(R).
runs([]) --> [].

run(X, N) --> [X], {var(N)}, !, run(X, M), {N is M+1}.
run(X, N) --> [X], {N > 0}, !, {M is N-1}, run(X, M).

```

There’s a more elegant solution using freeze/2 and when/2, but it leaves choicepoints: [Logically pure/impure predicate implementations in the std lib - #16 by peter.ludemann](https://swi-prolog.discourse.group/t/logically-pure-impure-predicate-implementations-in-the-std-lib/6317/16)

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [March 1, 2023, 9:04am UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/12 "2023-03-01T09:04:27Z")

</div>

> [@ridgeworks](#):
>
> I agree that mixing that with cuts is a potential disaster which can best be avoided by ensuring that “frozen” goals (including any `attr_unify_hook`’s) are deterministic.

That is not enough. It is typically hard to predict when a wakeup happens and thus whether it is before or after the cut. So, a cut may be reached because some constraint (or frozen goal, there is not much difference) that will fail has not yet been triggered. Had the constraint been triggered, then the cut would not have been reached. If you know exactly what you are doing you can deal with that. It remains a little shaky though ☹ Especially when it comes to portability as the moment when the wakeup happens vary and constraint solvers differ in their propagation strategy and thus may or may not have resolved some constraint at some point in the computation.

---

<div class="post-metadata">

**Author:** ![ridgeworks](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/ridgeworks/32/886_2.png) [@ridgeworks](https://swi-prolog.discourse.group/u/ridgeworks)\
**Post date:** [March 1, 2023, 4:21pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/13 "2023-03-01T16:21:16Z")

</div>

> [@jan](#):
>
> That is not enough. It is typically hard to predict when a wakeup happens and thus whether it is before or after the cut.

For this example, really? If the constraint was fired in head unification, can the `attr_unify_hook` be executed after the ‘`!`’ in the body? Is ‘`!`’ different than any other goal in this situation?

---

<div class="post-metadata">

**Author:** ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)\
**Post date:** [March 7, 2024, 1:58pm UTC](https://swi-prolog.discourse.group/t/freeze-interferes-with-multiple-solutions/6309/14 "2024-03-07T13:58:50Z")

</div>

Here’s a (hopefully clearer) example of freeze/2 (and when/2) making -\>/2 unsafe:

```prolog
freeze_test(A, B) :-
    ( A = 2
    -> B = two
    ; A = 1
    -> B = one
    ).

freeze_test2(A, B) :-
    ( A = 2,
        B = two
    ; A = 1,
        B = one
    ).

```

Results:

```prolog
?- freeze(A, member(A, [1, 2])), freeze_test(A, B).
A = 2,
B = two.

?- when(ground(A), member(A, [1, 2])), freeze_test(A, B).
A = 2,
B = two.

```

vs

```prolog
?- member(A, [1, 2]), freeze_test(A, B).
A = 1,
B = one ;
A = 2,
B = two.

?- freeze(A, member(A, [1, 2])), freeze_test2(A, B).
A = 2,
B = two ;
A = 1,
B = one ;
false.

```

I am wondering whether this can be made nicer than a huge _gotcha_ for the unwary - perhaps adding a runtime warning if the frozen/2 Goal succeeds more than once?

A fragile workaround, purely for example:

```prolog
freeze_test3(A, B) :-
    (frozen(A, freeze(A, Goal)) -> Goal ; true),
    ( A = 2
    -> B = two
    ; A = 1
    -> B = one
    ).

```

Results:

```prolog
?- freeze(A, member(A, [1, 2])), freeze_test3(A, B).
A = 1,
B = one ;
A = 2,
B = two.

```
