Once/1 should be deprecated!

Guys please discuss once/1 and friends here. I even don’t have an example
where once/1 is needed, yet there is alternative solution without once/1.

Edit 26.04.2021:
Ok a use case could be that it is frowned upon once/1+member/2, whereas
memberchk/2 would be ok? But then we dont have a nth0chk/3, etc…

To venture an idea relevant in my code:

I guess, once/1 seems to play two roles:

During forward processing to indicate that truth of a goal or query depends on proving the existence of one solution. Wouldn’t that be the paradigmatic case for once/1.

And during backwards process – backtracking – to avoid redoing the goal — since it proved to be true already. Is this actually happening?

And, i guess, a once should eliminate choice points … but should also not make the goal wrapped in the once fail upon backtracking – ideally, it should also not be called again (hence once)

Not sure I am thinking straight here :slight_smile:

Dan

Thanks for splitting the topic. Yes, you use once if you want a first solution of an otherwise nondet goal. This is in my experience hardly ever the case though. In a deterministic context one typically uses (semi-)deterministic sub-goals. There can be some exceptions, such as handling a line “Var=Value”. Now you could do

line_var_value(Line, Var, Value) :-
    once(sub_atom(Line, B, _, A, =)),
    sub_atom(Line, 0, B, _, Var),
    sub_atom(Line, _, A, 0, Value).

I think the above is perfectly ok code, although in practice I’d write

line_var_value(Line, Var, Value) :-
    (   sub_atom(Line, B, _, A, =))
    ->  sub_atom(Line, 0, B, _, Var),
        sub_atom(Line, _, A, 0, Value).
    ;   syntax_error(invalid_var_decl)
    ).

For example, all libraries and packages use once/1 68 times in 300Klines (that includes the definition and probably some more false hits), mostly in old(er) code.

1 Like

I think you recently posted that Goal -> true was a better performing equivalent to once(Goal) and, using vm_list and measuring the execution time, that indeed appears to be the case.

However, with a little love from the compiler, it would seem that the once form could be slightly better than the -> syntax.

Although it may not be used much, I prefer once to ->. It’s just easier to read and doesn’t have the slightly quirky semantics of ->. But (I think) I understand the performance advantages of the current -> .

library(apply_macros) causes once/1 to be rewritten as (Goal->true). That should give you the best of both worlds. And yes, in theory it could be a bit smarter providing direct VM support. That is more likely to make the system bigger and slower and harder to maintain than gain anything. The work by @dmchurch is likely to make profiling the VM a lot easier and might give more insight in the effect of specialized VM instructions. I think it pretty unlikely once/1 is a good candidate though.

Yes, that would do the job, although I couldn’t find any doc so I had to dig into the development repository. I agree that there would be little justification for specialized VMI’s for once.

But it does seem a bit arbitrary that ->, ;, and \+ all get special compiler treatment and once doesn’t; they’re all ISO control predicates.

Alternatively, why shouldn’t apply_macros (or at least the subset for system primitives) be the default in SWIP rather than an option that requires programmer intervention?

Aside: is this performance issue the only reason why the suggestion that once/1 be deprecated?

Eventually we need a more general inline strategy that is controlled by optimization modes. apply_macros is a crude quick first step borrowed from YAP.

1 Like

Perhaps, although in this particular case (not that it’s that compelling) I would argue that the compiler should already be optimizing as it does for -> and \+. Probably one reason it doesn’t is that it isn’t worthwhile to add a special VMI for it, since a slightly sub-optimal form can be built using ->.

Aside: The really compelling use case for me is something like once(N<0 ; N>1) when arithmetic is optimized; much more natural IMO than (N<0 -> true ; N>1).

I have no issue with a more general inline strategy as long as it doesn’t violate the “perfect is the enemy of good enough” principle.