[m-dev.] Fw: Replacement syntax for DCGs

Ralph Becket rafe at cs.mu.OZ.AU
Sat Dec 1 14:33:23 AEDT 2001

Peter Schachte, Saturday,  1 December 2001:
> > we would make ! an infix binary operator and write
> > 
> > io ! foo :-
> > 	p(...),
> > 	q(...),
> > 	io ! io__print(...),
> > 	r(...),
> > 	io ! io__print(...).
> Details, please.
> How can a new state be created (ie, how does a predicate without a state
> thread call one with one)?  How does one access and set the current value of a
> state thread?  What if a predicate is declared to take a state thread foo but
> I call it as bar!pred(...)?  Is it OK to mix clauses?  Can I write one clause
> with 4 arguments, andother with 2 and a state thread foo, and another with 2
> and a state thread bar?

_!_ is just syntactic sugar used within a clause definition to stand for
a threaded argument pair.  The DCG based expansion I outlined would work
just fine except that it doesn't cover thread reordering and local
threading.  I'll work out the rules for the complete expansion and post
them in a later e-mail.

The thread name has no meaning outside the context of the clause that
uses it.  That is, `io' in the above example could just as well be
renamed `foo' for all the difference it would make.

Similarly the following

	io ! 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]).

would have exactly the same expansion as

	write_list([],        IO0, IO) :- nl(IO0, IO).
	write_list([X]               ) --> print(X).
	io ! write_list([X1, X2 | Xs]) :-
		io ! print(X1),
		io ! write_list([Xs | Xs]).


	a ! write_list([]           ) :- a ! nl.
	b ! write_list([X]          ) :- b ! print(X).
	c ! write_list([X1, X2 | Xs]) :-
		c ! print(X1),
		c ! write_list([Xs | Xs]).

although it would be perverse to use the latter schemes.  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).  So different callers may call
write_list/3 above with
	..., iostate ! write_list(Xs), ...
	..., foo ! write_list(Xs), ...
	..., write_list(Xs, IO0, IO1), ...
or just
	..., write_list(Xs), ...
in a DCG context.

Given that, a new state thread is therefore initialised in exactly the
same way as with a DCG: you pass the initial value in as an argument.

That said, it may well be handy to allow named threads that do not occur
in the head.  For example

	p(...) :-
		x ! :=(InitialValueOfXThread),
		x ! a(...),
		x ! b(...),
		x ! c(...),
		x ! =(FinalValueofXThread),

The above syntax for accessing and setting the current value of a thread
seems okay to me, but I'm open to suggestions.

> Is there reason to believe that users are less likely to forget to put foo! in
> front of a goal than they are to forget to surround one with braces?  I don't
> see this proposal helping the error-prone nature of DCGs.  In fact, this
> proposal adds the complication of multiple possible threads, while users are
> unlikely to spell {} wrong.

Well, forgetting to supply an argument is a common problem in all
programming languages.  DCG notation, AFAICT, is the only notation to
cause problems when you forget to say you *don't* want an argument pair.

> I think it would be better to put the thread name at the end rather than the
> front (foo(X) ! bar rather than bar !  foo(X)) because it puts the thread,
> which is really just arguments, with the arguments of the goal.  It also puts
> the emphasis on the predicate name, rather than the thread name (which is less
> important).

This debate has been going on in the office for the last few days.
The options we've come up with are

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").

	io ! 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]).

	- using a prefix makes it immediately obvious that the call is
	- 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)'.
	- since the arguments are appended, it's slightly odd that the
	  thread names appear as prefixes.

2. Reverse the order, so one would write `print(X) ! io':

	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.

	- thread names appear in the same place as the arguments they
	  expand to.
	- it's less obvious which calls are `stateful' (the prefix form
	  leaps off the page, the suffix form is less obvious).

3. Include the thread names in the argument list, using ! as a prefix

	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).

Of these I find option 2 the least appealing.

> I'd also like to see some description of the mental model I'm supposed to have
> as I read and write code using this facility.  I agree with Fergus that
> defining it solely by translation isn't so helpful.

My mental model when using DCGs is usually something like having a
hidden global variable being manipulated.

The mental model I have in mind for the current proposal is that of
having local variables being updated destructively.

Neither is a particularly declarative way of thinking about things, but
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.

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.

> > The chaining expansion does impose an order on the argument pairs (e.g.
> > here io argument pairs must come before array argument pairs.)  This
> > doesn't seem like a big deal, but if it is, we can just make the
> > obvious change to the algorithm rather than using simple DCG expansion.
> I don't see a reason to require users to get the order right, and doing so
> makes the system more error-prone (though of course ordinary argument passing
> is just as error-prone).  I think it would be better to consider the threads
> passed to a predicate as a set.

Yes, we require users to get the order right for all the the other
arguments!  That doesn't seem to be a major source of pain and the
compiler is quick to spot the majority of such errors.

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.

> > If we want to access the values of the threaded parameters we can
> > use the existing DCG notation:
> > 
> > 	array ! =(X)	% Unifies X with the current array value.
> I think a functional-style syntax (ie, a term rather than a goal) for
> accessing the current value would be preferable.  I propose the syntax $array
> for access, and $array := Value for setting.

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 (!)

I suppose != and !:= would not be too bad.
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