[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