prologdcgiso-prolog

DCG Expansion: Is Steadfastness ignored?


Assume I have the following DCG rule:

 factor(X) --> "(", expr(X), ")".

Normally this would be translated to:

 factor(X, A, B) :-
    [40|C] = A, expr(X, C, D), [41|B] = D.

Would a Prolog system be allowed to translate it as follows, i.e.
to merge the unifications into the head and the goal?

 factor(X, [40|A], B) :-
    expr(X, A, [41|B]). 

If DCG expansion would not be steadfast, it wouldn't be allowed
to put [41|B] in the third argument of the expr call.

But I guess steadfastness is in place, so everything should be alright?

Bye

P.S.: For an informal definition of steadfastness see:
Richard O'Keefe, 2009:
"As the inventor of the term "steadfast" in Prolog programming,
I ought to be in favour of it. Steadfastness basically
means that you cannot force a predicate down the wrong path
by filling in output arguments wrongly." (source)

P.S.S.: For the other DCG translation see for example the newest
DCG standard proposal. The appendix contains a DCG translator
source code:
ISO/IEC DTR 13211–3:2006
Definite clause grammar rules
Klaus Daessler
November 20, 2012
N238 DIN Draft 2012-11-20


Solution

  • Your translation is a valid one. It does not influence steadfastness. However, it still might not be very desirable. But this depends on the precise implementation you employ. Consider:

    opcl --> "".
    opcl --> "(", opcl, ")".
    

    With Prolog flag double_quotes set to chars, the second clause now might be expanded to

    opcl1(['('|S0],S) :-
       opcl1(S0,S1),
       S1 = [')'|S].
    

    or

    opcl2(['('|S0],S) :-
       opcl2(S0,[')'|S]).
    

    Now consider the goal phrase(opcl,"(((())))".

    On common machines like WAM (e.g. YAP, SICStus), ZIP (SWI), TOAM Jr. (B):

    opcl1

    opcl1 will simply test the validity of the list, using the call-stack for the procedural control. On success, no cons-cell will have been created, and the call-stack will be empty again. Actually, above implementations are not able to detect that the goal is determinate, so they will leave one choice-point open. You can see this on the toplevel:

    ?- phrase(opcl,"(((())))").
       true
    ;  false.
    

    This choice-point can be cleanly and safely removed with call_semidet/1.

    opcl2

    opcl2 will create four instances of [')'|_] on the heap that need to be reclaimed by GC. But they are saving the call-stack. That is, there will be only tail-recursive calls which are very efficiently handled on WAM, minimally less efficiently on TOAM Jr. and relatively costly on SWI.

    Things become even more costly when we are considering execution with occurs-check. In Qu-Prolog it is always on, and in SWI, XSB, and CX you can enable it with a flag like so:

    ?- phrase(opcl,Xs,Xs).
       true
    ;  Xs = ['(',')'|Xs]
    ;  Xs = ['(','(',')',')'|Xs]
    ;  ... .
    ?- set_prolog_flag(occurs_check,true).
       true.
    ?- phrase(opcl,Xs,Xs).
       true
    ;  loops.
    

    SWI does not need to perform a single occurs-check for opcl1. But it does so for each ) in opcl2.

    So for these machines, your translation does not appear favorable. But it might be of interest for another machine, where there is no separate call-stack and which is not based on continuations.

    call//1

    Your translation will change the precise connection within call//1. However, the goal within call//1 must always be written in a manner such that it is steadfast! Otherwise, the difference could be seen already when calling phrase(call(Cont),Xs0,Xs)! For a conforming Cont this will be the same as phrase(call(Cont),Xs0,XsC), XsC=Xs


    As to your quote about steadfastness: It is a very informal definition of the notion. After all, what means "wrongly"?

    The best definition of steadfastness for phrase/3 so far, I am aware of is:

    phrase(NT, Xs0,Xs) and phrase(NT, Xs0, XsC), XsC = Xs with XsC a fresh new variable, are always the same.