<div dir="auto">Hi,<div dir="auto">The latest conversation between emacstheviking and Zoltan about how the compiler handles determinism in disjuncts is quite illuminating and useful to learners, I think. I went through all this about a year ago when I began learning Mercury more seriously and I think all learners do or will experience such situations.</div><div dir="auto">I think it would be a Good Idea to add a separate practical example to J. Fondren's excellent crash course to drive the point home.</div><div dir="auto">Perhaps it would also help to strongly underline that the way Mercury works in disjuncts is a design consequence of the elimination of prolog cuts, and not just a form of compiler blindness. I now that prolog is not the core interest of the Mercury dev team but practically people tackling Mercury are coming from a prolog experience and the prolog to Mercury transition guide is a bit short on practical issues in this respect. </div><div dir="auto">Having read R. O'Keefe's The Craft of Prolog in the past does help (Mercury's design is a direct response to prolog shortcomings reviewed in this book), but not all learners read it and a few more Mercury examples would not harm.</div><div dir="auto">Fabrice</div><div dir="auto"><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Le dim. 4 juil. 2021 à 7:34 PM, <<a href="mailto:users-request@lists.mercurylang.org">users-request@lists.mercurylang.org</a>> a écrit :<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Send users mailing list submissions to<br>
<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a><br>
<br>
To subscribe or unsubscribe via the World Wide Web, visit<br>
<a href="https://lists.mercurylang.org/listinfo/users" rel="noreferrer noreferrer" target="_blank">https://lists.mercurylang.org/listinfo/users</a><br>
or, via email, send a message with subject or body 'help' to<br>
<a href="mailto:users-request@lists.mercurylang.org" target="_blank" rel="noreferrer">users-request@lists.mercurylang.org</a><br>
<br>
You can reach the person managing the list at<br>
<a href="mailto:users-owner@lists.mercurylang.org" target="_blank" rel="noreferrer">users-owner@lists.mercurylang.org</a><br>
<br>
When replying, please edit your Subject line so it is more specific<br>
than "Re: Contents of users digest..."<br>
<br>
<br>
Today's Topics:<br>
<br>
1. Re: Determinism issue with parser (Sean Charles (emacstheviking))<br>
2. Re: Determinism issue with parser (Zoltan Somogyi)<br>
3. Re: Determinism issue with parser (Volker Wysk)<br>
4. Re: Determinism issue with parser (Zoltan Somogyi)<br>
<br>
<br>
----------------------------------------------------------------------<br>
<br>
Message: 1<br>
Date: Sun, 4 Jul 2021 17:31:59 +0100<br>
From: "Sean Charles (emacstheviking)" <<a href="mailto:objitsu@gmail.com" target="_blank" rel="noreferrer">objitsu@gmail.com</a>><br>
To: Zoltan Somogyi <<a href="mailto:zoltan.somogyi@runbox.com" target="_blank" rel="noreferrer">zoltan.somogyi@runbox.com</a>><br>
Cc: users <<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a>><br>
Subject: Re: [m-users.] Determinism issue with parser<br>
Message-ID: <<a href="mailto:F5C1D814-E1A5-46C5-BE48-E938C47B7C7E@gmail.com" target="_blank" rel="noreferrer">F5C1D814-E1A5-46C5-BE48-E938C47B7C7E@gmail.com</a>><br>
Content-Type: text/plain; charset="utf-8"<br>
<br>
Zoltan, your replies have always helped and continue to do so.<br>
<br>
If by "one" you mean one determinism declaration, they why do you<br>
"change one and recompile"?<br>
<br>
The answer; simple frustration at being fully cognisant of my own ignorance, that’s why.<br>
<br>
I —understand— the reasons why it is telling me I am wrong but then chaos seems to erupt inside my head. It’s —my— decision as to the determinism of the code I write, IIUIC that’s the point of declaring it, then the compiler tells me otherwise when it knows better (often).<br>
<br>
As for using solutions/1, it crossed my mind but for me that’s a biiig code smell and I refused to do it.<br>
<br>
Part of my confusion is understanding what it means to have more than one solution if the inputs are the same?!?! How can that be? For example, in this predicate where I check for a body form:<br>
<br>
:- pred parse_body_term(snode::out, astcon::in, astcon::out) is multi.<br>
<br>
parse_body_term(Term, !A) :-<br>
(<br>
parse_atom(Term, !A)<br>
;<br>
parse_list(Term, !A)<br>
;<br>
parse_map(Term, !A)<br>
;<br>
parse_feltcall(Term, !A)<br>
;<br>
parse_fncall(Term, !A)<br>
;<br>
parse_sexp(Term, !A)<br>
).<br>
<br>
I thought that this was semidet because either one of those disjuncts succeed or they all fail, I don’t understand why it says multi, multi is ‘can’t fail, >1 solutions’ but if I consume tokens in the parse_map() disjunct branch, how can it then come back for more? I just don’t get it, that’s something that really confuses me at the moment. Hence my question ‘what does it mean to have more than one solution?’. <br>
<br>
det: for example ‘*’, given x*y, the result is deterministic.<br>
semidet: for example, given x/y, if y is nonzero the result is deterministic, if y is zero presumably it fails. pedagogical usage here that’s all.<br>
<br>
for me the confusion starts with `multi`, to have more than one solution surely it has to be called more than once with different inputs or is stepping through a set of facts?<br>
<br>
I think if I had managed to get mdb to work it might help to single step the code but I tried for a few hours and emacs version was wrong, lots of problems etc so I went back to ’thinking’ about it and trace[] all over the place.<br>
<br>
Not a good place to be at the moment but things worthwhile are never easy to attain.<br>
<br>
Thanks.<br>
<br>
<br>
> On 4 Jul 2021, at 16:56, Zoltan Somogyi <<a href="mailto:zoltan.somogyi@runbox.com" target="_blank" rel="noreferrer">zoltan.somogyi@runbox.com</a>> wrote:<br>
> <br>
> <br>
> 2021-07-05 01:21 GMT+10:00 "Volker Wysk" <<a href="mailto:post@volker-wysk.de" target="_blank" rel="noreferrer">post@volker-wysk.de</a>>:<br>
>> I've built a big parser for my project, too. It should always produce one<br>
>> solution; it can't fail. But the compiler can't infer that it is<br>
>> deterministic, for many parts of it. (That would be undecidable.) <br>
>> <br>
>> So I've resigned to this state of affairs. I just collect all the (exactly<br>
>> one) solutions with solutions/2 and produce an error message in case there<br>
>> should be more than one, which would be a bug in my parser.<br>
> <br>
> That works, but I would consider it fragile. The reason is that convincing<br>
> someone (the next maintainer of the parser, perhaps) that there will be<br>
> exactly one overall solution requires a single correctness argument over<br>
> the entire code, and humans are not very reliable as reasoning machines;<br>
> the longer the argument, the higher the probability of error.<br>
> <br>
> I would instead add disambiguation code to the places in the parser<br>
> than can introduce (as far as the compiler is concerned) the possibility<br>
> of nondeterminism. You can you can see why there is no such possibility;<br>
> I would try to make the compiler that too. In most cases, this is not too hard,<br>
> at least in my experience.<br>
> <br>
> This approach replaces a single bug, global correctness argument with lots of<br>
> small, local correctness arguments, which can be understood, documented,<br>
> and if need be fixed, much more easily. Modularity in correctness arguments,<br>
> if you will.<br>
> <br>
> Zoltan.<br>
<br>
-------------- next part --------------<br>
An HTML attachment was scrubbed...<br>
URL: <<a href="http://lists.mercurylang.org/archives/users/attachments/20210704/043b2328/attachment-0001.html" rel="noreferrer noreferrer" target="_blank">http://lists.mercurylang.org/archives/users/attachments/20210704/043b2328/attachment-0001.html</a>><br>
<br>
------------------------------<br>
<br>
Message: 2<br>
Date: Mon, 05 Jul 2021 03:09:42 +1000 (AEST)<br>
From: "Zoltan Somogyi" <<a href="mailto:zoltan.somogyi@runbox.com" target="_blank" rel="noreferrer">zoltan.somogyi@runbox.com</a>><br>
To: "Sean Charles" <<a href="mailto:objitsu@gmail.com" target="_blank" rel="noreferrer">objitsu@gmail.com</a>><br>
Cc: users <<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a>><br>
Subject: Re: [m-users.] Determinism issue with parser<br>
Message-ID: <E1m05ck-0002LB-Kd@rmmprod06.runbox><br>
Content-Type: text/plain; charset="utf-8"<br>
<br>
<br>
2021-07-05 02:31 GMT+10:00 "Sean Charles (emacstheviking)" <<a href="mailto:objitsu@gmail.com" target="_blank" rel="noreferrer">objitsu@gmail.com</a>>:<br>
> Part of my confusion is understanding what it means to have more than one solution if the inputs are the same?!?!<br>
<br>
I am not sure what you are trying to say with "if the inputs are the same".<br>
Determinism is about the number of solutions you can get *for a given set of input values*.<br>
<br>
> How can that be? For example, in this predicate where I check for a body form:<br>
> <br>
> :- pred parse_body_term(snode::out, astcon::in, astcon::out) is multi.<br>
> <br>
> parse_body_term(Term, !A) :-<br>
> (<br>
> parse_atom(Term, !A)<br>
> ;<br>
> parse_list(Term, !A)<br>
> ;<br>
> parse_map(Term, !A)<br>
> ;<br>
> parse_feltcall(Term, !A)<br>
> ;<br>
> parse_fncall(Term, !A)<br>
> ;<br>
> parse_sexp(Term, !A)<br>
> ).<br>
> <br>
> I thought that this was semidet because either one of those disjuncts succeed or they all fail,<br>
<br>
You may know that, but the compiler does not. As I mentioned in my previous email,<br>
the compiler does not even *try* to figure out whether the success of one of these calls<br>
rules out the success of the other calls. Since I presume none of the called predicates<br>
are declared to have determinism "failure" or "erroneous", it simply assumes that<br>
they can all succeed. As far as the compiler is concerned, any given call to parse_body_term<br>
can generate six solutions, one from each disjunct, even if each call generates a maximum of one solution.<br>
<br>
> I don’t understand why it says multi, multi is ‘can’t fail, >1 solutions’ but if I consume tokens<br>
> in the parse_map() disjunct branch, how can it then come back for more?<br>
<br>
There are two answers to that.<br>
<br>
First, a call to parse_x, whatever x is, may consume tokens in the sense of stepping over them,<br>
but this has effect only during forward execution. Upon backtracking, the value of !.A that the<br>
next disjunct will start with *will* be the same as its original value on entry to the disjunction,<br>
and thus will contain the "consumed" tokens as well as all the following tokens.<br>
<br>
Second, determinism analysis does not know anything about tokens, consumed or not,<br>
and would not care if it did. The predicates called in the later disjuncts have declared modes<br>
that say they can succeed, so *regardless of anything else*, determinism analysis assumes<br>
they can succeed. That's it. Determinism analysis of disjunctions is *that* simple.<br>
<br>
> I just don’t get it, that’s something that really confuses me at the moment. Hence my question ‘what does it mean to have more than one solution?’. <br>
<br>
Section 6.2 gives the *complete* algorithm.<br>
<br>
Your problem is that you attributing to determinism analysis<br>
powers and reasoning skills it simply does not have.<br>
<br>
In the case of the above disjunction, if there is a later disjunct than e.g. the one that calls parse_atom,<br>
then the compiler will assume it *can* succeed and generate a solution. That's it; there is<br>
nothing more. Determinism analysis does not know, nor does it care, whether *you* know,<br>
or at least believe, that no later disjunct would generate a solution.<br>
<br>
> det: for example ‘*’, given x*y, the result is deterministic.<br>
> semidet: for example, given x/y, if y is nonzero the result is deterministic, if y is zero presumably it fails. pedagogical usage here that’s all.<br>
<br>
I have no idea what you are trying to say here. In any case, integer division is det, though it throws<br>
an exception if the divisor is zero.<br>
<br>
> for me the confusion starts with `multi`, to have more than one solution surely it has to be called more than once with different inputs<br>
<br>
No, that is wrong. Determinism is about how many solutions you can get for *one* call.<br>
Given a predicate p(in, out) whose definition is<br>
<br>
p(1, 11).<br>
p(1, 12).<br>
<br>
the one call p(1, X) will generate two solutions, X = 11 and X = 12.<br>
<br>
> or is stepping through a set of facts?<br>
<br>
Determinism analysis does not know, or care, whether a solution comes from a fact or not.<br>
<br>
> I think if I had managed to get mdb to work<br>
<br>
You can't run mdb until your program has been compiled, and it won't compile until<br>
you fix all the determinism errors.<br>
<br>
I see two ways to fix your code above. The easier but worse approach would to write<br>
something like<br>
<br>
parse_body_term(Term, !A) :-<br>
( if parse_atom(TermPrime, !A) then<br>
Term = TermPrime<br>
else if parse_list(TermPrime, !A) then<br>
Term = TermPrime<br>
else if parse_map(Term, !A) then<br>
...<br>
else<br>
parse_sexp(Term, !A)<br>
).<br>
<br>
This will tell the compiler that if you can find e.g. a list in the input,<br>
then you want to keep the list, and not look for any more solutions.<br>
<br>
The harder but better solution is available only if the sets of tokens<br>
that atoms, lists, maps etc can start with is disjoint. If that is the case,<br>
then I would write parse_body_term as (a) get the first token, then<br>
(b) switch on that token.<br>
<br>
The second approach is always available in LL(1) grammars, but<br>
of course not all grammars are LL(1).<br>
<br>
Zoltan.<br>
<br>
------------------------------<br>
<br>
Message: 3<br>
Date: Sun, 04 Jul 2021 19:21:11 +0200<br>
From: Volker Wysk <<a href="mailto:post@volker-wysk.de" target="_blank" rel="noreferrer">post@volker-wysk.de</a>><br>
To: <a href="mailto:zoltan.somogyi@runbox.com" target="_blank" rel="noreferrer">zoltan.somogyi@runbox.com</a>, Sean Charles <<a href="mailto:objitsu@gmail.com" target="_blank" rel="noreferrer">objitsu@gmail.com</a>>, users<br>
<<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a>><br>
Subject: Re: [m-users.] Determinism issue with parser<br>
Message-ID:<br>
<<a href="mailto:ecfa13776f3097af6bfcb6e85d2a4db79a5a1488.camel@volker-wysk.de" target="_blank" rel="noreferrer">ecfa13776f3097af6bfcb6e85d2a4db79a5a1488.camel@volker-wysk.de</a>><br>
Content-Type: text/plain; charset="utf-8"<br>
<br>
Am Montag, den 05.07.2021, 01:56 +1000 schrieb Zoltan Somogyi:<br>
> 2021-07-05 01:21 GMT+10:00 "Volker Wysk" <<a href="mailto:post@volker-wysk.de" target="_blank" rel="noreferrer">post@volker-wysk.de</a>>:<br>
> > I've built a big parser for my project, too. It should always produce one<br>
> > solution; it can't fail. But the compiler can't infer that it is<br>
> > deterministic, for many parts of it. (That would be undecidable.) <br>
> > <br>
> > So I've resigned to this state of affairs. I just collect all the (exactly<br>
> > one) solutions with solutions/2 and produce an error message in case there<br>
> > should be more than one, which would be a bug in my parser.<br>
> <br>
> That works, but I would consider it fragile. The reason is that convincing<br>
> someone (the next maintainer of the parser, perhaps) that there will be<br>
> exactly one overall solution requires a single correctness argument over<br>
> the entire code, and humans are not very reliable as reasoning machines;<br>
> the longer the argument, the higher the probability of error.<br>
<br>
The parser has fallbacks built in. A paragraph that isn't something else, is<br>
a text paragraph. An inline (something which occurs inside a paragraph) that<br>
isn't something special, is a simple word. A bug, which would cause a<br>
special case not to be recognized, causes the respective text to end up in<br>
the fallback data.<br>
<br>
The special cases are all in if-then-else-if-then-else etc. constructs. So<br>
there can be only be one matching. It's a parser for the markup language of<br>
a wiki engine. I think this is robust because of the language being parsed<br>
is like that. <br>
<br>
But I'd love to have the compiler understand the fact that it's<br>
deterministic. :-)<br>
<br>
> I would instead add disambiguation code to the places in the parser<br>
> than can introduce (as far as the compiler is concerned) the possibility<br>
> of nondeterminism. You can you can see why there is no such possibility;<br>
> I would try to make the compiler that too. In most cases, this is not too hard,<br>
> at least in my experience.<br>
<br>
Hmm... I use my meta-predicate "repeatmax0" (and some variations of it) a<br>
lot. But see further down first, you don't really need all the details:<br>
<br>
<br>
% Something repeated zero to several times. Return the collected return <br>
% values as a list.<br>
<br>
:- pred repeat0(pred(T, list(char), list(char)), list(T), <br>
list(char), list(char)).<br>
:- mode repeat0(pred(out, in, out) is det, out, in, out) is multi.<br>
:- mode repeat0(pred(out, in, out) is semidet, out, in, out) is multi.<br>
:- mode repeat0(pred(out, in, out) is multi, out, in, out) is multi.<br>
:- mode repeat0(pred(out, in, out) is nondet, out, in, out) is multi.<br>
<br>
<br>
% The longest sequence of repetitions of the specified thing, which returns<br>
% something. Its return values are returned as a list. Succeeds for zero <br>
% repetitions (with an empty list).<br>
<br>
:- pred repeatmax0(pred(T, list(char), list(char)), list(T), <br>
list(char), list(char)).<br>
:- mode repeatmax0(pred(out, in, out) is det, out, in, out) is nondet.<br>
:- mode repeatmax0(pred(out, in, out) is semidet, out, in, out) is nondet.<br>
:- mode repeatmax0(pred(out, in, out) is multi, out, in, out) is nondet.<br>
:- mode repeatmax0(pred(out, in, out) is nondet, out, in, out) is nondet.<br>
<br>
<br>
The compiler told me that repeatmax0 is nondet. The implementation is<br>
simple:<br>
<br>
<br>
repeat0(_, []) --> [].<br>
<br>
repeat0(Pars, Values) --><br>
{ Values = [Val|Values1] },<br>
Pars(Val),<br>
repeat0(Pars, Values1).<br>
<br>
repeatmax0(Pars, Values) --><br>
repeat0(Pars, Values), % XXX<br>
( if not Pars(_)<br>
then []<br>
else end<br>
).<br>
<br>
<br>
And there's the parser for the end of the input:<br>
<br>
<br>
:- pred end(list(char)::in, list(char)::out) is semidet.<br>
<br>
end([], []).<br>
<br>
<br>
The compiler told me, that the call to repeat0 (marked with "% XXX" above)<br>
can succeed more than once, which is correct.<br>
<br>
Taking your hint about disambiguation code, I was able to reformulate the<br>
repeatmax0 predicate such that, repeatmax0 becomes deterministic for a<br>
deterministic input parser:<br>
<br>
<br>
:- mode repeatmax0(pred(out, in, out) is det, out, in, out) is det.<br>
<br>
repeatmax0(Pars, Values) --><br>
( if Pars(Val)<br>
then { Values = [Val|Vals] },<br>
repeatmax0(Pars, Vals)<br>
else { Values = [] }<br>
).<br>
<br>
<br>
I'm still not sure what you mean by "disambiguation code", though.<br>
<br>
<br>
> This approach replaces a single bug, global correctness argument with lots of<br>
> small, local correctness arguments, which can be understood, documented,<br>
> and if need be fixed, much more easily. Modularity in correctness arguments,<br>
> if you will.<br>
<br>
Thanks!<br>
<br>
Volker<br>
-------------- next part --------------<br>
A non-text attachment was scrubbed...<br>
Name: signature.asc<br>
Type: application/pgp-signature<br>
Size: 833 bytes<br>
Desc: This is a digitally signed message part<br>
URL: <<a href="http://lists.mercurylang.org/archives/users/attachments/20210704/672d4de7/attachment-0001.sig" rel="noreferrer noreferrer" target="_blank">http://lists.mercurylang.org/archives/users/attachments/20210704/672d4de7/attachment-0001.sig</a>><br>
<br>
------------------------------<br>
<br>
Message: 4<br>
Date: Mon, 05 Jul 2021 03:34:16 +1000 (AEST)<br>
From: "Zoltan Somogyi" <<a href="mailto:zoltan.somogyi@runbox.com" target="_blank" rel="noreferrer">zoltan.somogyi@runbox.com</a>><br>
To: "Volker Wysk" <<a href="mailto:post@volker-wysk.de" target="_blank" rel="noreferrer">post@volker-wysk.de</a>>, "Sean Charles"<br>
<<a href="mailto:objitsu@gmail.com" target="_blank" rel="noreferrer">objitsu@gmail.com</a>>, "users" <<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a>><br>
Subject: Re: [m-users.] Determinism issue with parser<br>
Message-ID: <E1m060W-0004Bs-VW@rmmprod06.runbox><br>
Content-Type: text/plain; charset="utf-8"<br>
<br>
<br>
2021-07-05 03:21 GMT+10:00 "Volker Wysk" <<a href="mailto:post@volker-wysk.de" target="_blank" rel="noreferrer">post@volker-wysk.de</a>>:<br>
> The compiler told me that repeatmax0 is nondet. The implementation is<br>
> simple:<br>
> <br>
> <br>
> repeat0(_, []) --> [].<br>
> <br>
> repeat0(Pars, Values) --><br>
> { Values = [Val|Values1] },<br>
> Pars(Val),<br>
> repeat0(Pars, Values1).<br>
> <br>
> repeatmax0(Pars, Values) --><br>
> repeat0(Pars, Values), % XXX<br>
> ( if not Pars(_)<br>
> then []<br>
> else end<br>
> ).<br>
<br>
This is nondet because the compiler can't see any reason<br>
why the second and third clauses could not both generate<br>
a solution.<br>
<br>
> Taking your hint about disambiguation code, I was able to reformulate the<br>
> repeatmax0 predicate such that, repeatmax0 becomes deterministic for a<br>
> deterministic input parser:<br>
> <br>
> <br>
> :- mode repeatmax0(pred(out, in, out) is det, out, in, out) is det.<br>
> <br>
> repeatmax0(Pars, Values) --><br>
> ( if Pars(Val)<br>
> then { Values = [Val|Vals] },<br>
> repeatmax0(Pars, Vals)<br>
> else { Values = [] }<br>
> ).<br>
> <br>
> <br>
> I'm still not sure what you mean by "disambiguation code", though.<br>
<br>
I meant any code that tells the compiler that there will be only one solution<br>
even though its old version would be able to generate more than one solution<br>
as far as the compiler is concerned.<br>
<br>
Your code immediately above does this: by replacing the original second<br>
and third clauses (an implicit disjunction) with an if-then-else, you told the compiler<br>
that there will be a solution from either then then-part or the else-part, BUT NOT BOTH.<br>
So it seems you understood my hint sufficiently well, even if you did not think so :-)<br>
<br>
Zoltan.<br>
<br>
------------------------------<br>
<br>
Subject: Digest Footer<br>
<br>
_______________________________________________<br>
users mailing list<br>
<a href="mailto:users@lists.mercurylang.org" target="_blank" rel="noreferrer">users@lists.mercurylang.org</a><br>
<a href="https://lists.mercurylang.org/listinfo/users" rel="noreferrer noreferrer" target="_blank">https://lists.mercurylang.org/listinfo/users</a><br>
<br>
<br>
------------------------------<br>
<br>
End of users Digest, Vol 83, Issue 2<br>
************************************<br>
</blockquote></div>