[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]).
or
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), ...
or
..., foo ! write_list(Xs), ...
or
..., 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]).
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.
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.
Pros:
- thread names appear in the same place as the arguments they
expand to.
Cons:
- 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
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).
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