Freeze interferes with multiple solutions

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 @j4n_bur53).

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