[m-dev.] Fw: Replacement syntax for DCGs
Ralph Becket
rafe at cs.mu.OZ.AU
Mon Dec 3 12:07:09 AEDT 2001
Peter Schachte, Sunday, 2 December 2001:
> On Sat, Dec 01, 2001 at 02:33:23PM +1100, Ralph Becket wrote:
> > So the name the caller uses to name a thread and the name used by
> > the callee do not have to match (and either the caller or the callee
> > may elect not to use the _!_ notation, just as with DCGs).
>
> Do you have a reason for not requiring them to match?
We don't currently require that explicit io state threading use variable
names IO0, IO1, IO2, ..., nor do I think we should. It is a useful
convention, but occasionally inappropriate. For instance, higher order
predicates. Consider list__foldl2: should we have a separate version
for every possible named thread? That is, should we have
io__foldl2(P, Xs, Acc0, Acc)
io__foldl2(P, Xs, !io)
io__foldl2(P, Xs, !foo)
io__foldl2(P, Xs, !bar)
...
(using style option 3 from my previous post.)
> Pros for requiring them to match:
>
> 1. Makes the code easier to understand (for the same reason it's a
> good idea to use the same variable names for the same things in
> different predicates).
This can be solved using a sensible convention. As I said above, I
think it's a bad thing to require in general.
> 2. Better error checking (if someone uses different names, it's not
> unlikely they're calling the wrong predicate or passing the wrong
> thing). Think of it like type checking, but tighter. Eg, you may
> pass many integers to a predicate, but you'll have much fewer called
> "max_depth".
My guess is that type checking will catch almost all of these errors.
We could extend your suggestion to cover variable names as well, which
would be a terrible idea ("any number you like, as long as we all call
it N" :-).
> 3. It makes it easy to treat the threads passing through a
> predicate as a set: just sort them by name. Then the caller and
> callee know how to order them. This makes the whole thing much less
> error prone, which I see as half the reason for having a feature
> like this.
Again, this would make higher order programming more painful than
necessary. I don't want to have to put a wrapper around every higher
order call just to work around whatever lexical convention was adopted
by the library writer.
I'm afraid I'm totally unconvinced by the error-detection argument. If
the argument does hold, then why should it just apply to named state
threads? But once it is extended to ordinary variables, it becomes a
ridiculous straightjacket.
> > 1. stick to the original proposal, adopting a kind of OO-style
> > reading (e.g. taking `io ! print(...)' to mean something like
> > "invoke the print(...) method on the `io' state thread").
>
> I don't think that reading extends very well to multiple threads.
I agree it's a weakness, but I don't think it's a showstopper.
> > Pros: - using a prefix makes it immediately obvious that the
> > call is `stateful'; - the arguments indicated by the prefix are
> > appended in the same order as they appear, so `a ! b ! p(X)'
> > will expand to `p(X, A0, A1, B0, B1)'. Cons: - since the
> > arguments are appended, it's slightly odd that the thread names
> > appear as prefixes.
>
> Much more importantly to me:
>
> - It puts the emphasis on the data, rather than the relation.
> Since threaded data is often really beside the point (eg,
> passing an io__state pair to a predicate just so it can print
> out error messages), that 's the wrong thing to emphasize.
The other side of the coin is that it makes it explicit that lexical
order is important for _!_ goals in a way that is immediately obvious.
> - With different arguments having different (or no) thread
> arguments, the code will get ugly and hard to read. Eg:
>
> io ! depth ! do_something([]).
> io ! depth ! do_something([foo(X)|Tail]) :-
> io ! io__write(...),
> depth ! increment,
> extract_info(X, Y),
> Y > 7,
> io ! depth ! do_something(Tail).
You have a point, although I'd expect 90%+ of code samples that use the
facility to have just one thread, in which case it's not ugly at all.
> the ***. Compare that to:
>
> do_something([]) ! io ! depth.
> do_something([foo(X)|Tail]) ! io ! depth :-
> io__write(...) ! io,
> increment ! depth,
> extract_info(X, Y),
> Y > 7,
> do_something(Tail) ! io ! depth.
Or even
do_something([], !io, !depth).
do_something([foo(X)|Tail], !io, !depth) :-
io__write(..., !io),
increment(!depth),
extract_info(X, Y),
Y > 7,
do_something(Tail, !io, !depth).
Are we edging towards agreeing on option 3?
> > 3. Include the thread names in the argument list, using ! as a
> > prefix operator:
> >
> > write_list([] , !io) :- nl(!io).
> > write_list([X] , !io) :- print(X, !io).
> > write_list([X1, X2 | Xs], !io) :-
> > print(X1, !io),
> > write_list([Xs | Xs], !io).
>
> Hmmm. Not bad. That suggests another alternative: extend the named
> argument syntax for terms to work for predicates and functions, too.
> I'll have to think about that.
>
> > then my feeling is that DCGs et al. are used when it's easier to
> > think about a piece of code in partially imperative terms.
>
> I think of it as using the lexical order of goals to convey part of
> the meaning of a clause.
A case of violent agreement (you say potato, I say spud).
> > As far as definition by translation goes, that's just the way I
> > think of DCGs, rather than w.r.t. some meta-interpreter. I think
> > insisting on having a simple meta-interpreter model is less
> > important than having a clear mental picture of how code using the
> > new syntax would work.
>
> I find it important to have a way to think about each part of a
> clause. I'd like to think of
>
> foo(X) ! y
>
> as meaning something like "execute foo(X) transforming y."
That's what I had in mind. The OO-ish interpretation was suggested by
Fergus and Mark.
> > I'd rather not adopt the set-of-threads philosophy, since it would
> > make the transformation more complex, thereby making higher order
> > programming with this notation much harder.
>
> As I pointed out above, if it is required to use the same name in call
> and clause head, you can just sort them to get the order right.
Okay, I'm strongly against this requirement for the reasons I gave
above.
> > Personally I'd rather just have one symbol (!) for state threading.
> > And my loathing for Perl is such that I can't stand the idea of
> > using $array (!)
>
> Syntactic parsimony is a worthy ambition, but I still think we could
> have a functional version of value access. Maybe !!depth to get the
> current value of depth? I think != looks too much like not equal.
> !:= for assignment is OK.
>
> I also agree with Zoltan that it's important to have read-only threads
> (only one argument added to the predicate). Occasionally one will
> want a write-only thread, too.
[Going back to my Prolog roots] How about
!+array - reads the current array thread value and
!-array - sets the subsequent array thread value?
- Ralph
--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to: mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions: mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------
More information about the developers
mailing list