[m-dev.] Re: Visual vs total arity clash example [fwd]

Ralph Becket rbeck at microsoft.com
Fri Jan 28 21:40:49 AEDT 2000


> From: Peter Schachte [mailto:schachte at cs.mu.OZ.AU]
> 
> Yes, I was wrong about that one.  The order doesn't matter, as long as
> a predicate p with hidden arguments a and b can all q with hidden
> arguments b and a.  My implementation made this easier by sorting the
> globals, but it's not necessary.

Great - I vote for keeping this an obvious source -> source
transformation.

> > I hope this is just a demonstration and not a suggested coding
> > style :)  Here EDCGs have made things worse rather than better.
> 
> [...]
> 
> Ok, you're thinking I've just described FORTRAN or something equally
> horrible.  Not so.  The main problems with procedural programming like
> this are that data structures intended as inputs may be modified
> without your knowing it, and since side effects can't be predicted,
> compilers can't optimize code very well.  With EDCGs, you only need
> look at the predicate declaration to know what a predicate call might
> change.  And since the compiler has this information as well, it's
> free to optimize.

Well, (1) there are several decent languages out there that do allow you
to specify the modes of various arguments to procedures (e.g. Modula-3
has READONLY and VAR parameter modes) and (2) we most definitely are
writing imperative code here: (,) is a sequencing operation, not a
logical connective, and code written in this style does have side-
effects in the imperative sense.

We want to introduce EDCGs for the same reason the Haskell people 
introduced the `do' notation: sometimes it just is easier to see what's
going on when written in an imperative style.

> I think with this model, the above code is a lot easier to understand
> than if you try to think of how it will be expanded.  Think of it as
> this:  in order to put [C|Cs] on the front of $list_acc, we first put
> Cs on the front, and then put C on the front of that.  But note that
> this predicate should really have been called prepend/1, or maybe
> accumulate_front/1.

Sure, I understand it: it's just imperative code.  But in this case
(i.e. recursion) I think EDCGs are more obscure than the equivalent
C or whatever:

void prepend(list xs, list *acc) {
	if(!empty(xs)) {
		prepend(tail(xs), acc);
		*acc = cons(head(xs), acc);
	}
}

This is slightly clearer because it's obvious in the recursive call
that acc is part of the process.  In the mercury version,

prepend(Xs) -->>
	(	Xs = []
	;	Xs = [H | T], prepend(T), $=acc = [H | $acc]
	).

it is far from obvious that the call to prepend has anything to do
with acc; the problem is worse if we're calling some different
predicate that changes acc.  These variables aren't locals, they're
almost globals!  Ack!

This is one of the reasons why I'd like to be obliged to
specify what variables are being passed to a call *at the call site*,
e.g.

prepend(Xs) -->>
	(	Xs = []
	;	Xs = [H | T], prepend(T) + changes(acc), $=acc = [H | $acc]
	).

> > {REQUEST: can we change `changed' to `changes'?  It's a
> > declaration, not a statement.)
> 
> Good point.  How about `uses' `changes' and `produces'?  Or even `in'
> `out' and `inout'?

in, out & inout (or, better, `var'?) would be much preferable!

Hmm, let's see what foldl2 looks like with vars and call-site annotation
in Ralph's New Syntax:

:- pred foldl2(pred(T) + var(x, y), list(T)) + var(x, y).

foldl2(P, Xs) + var(x, y) -->>
	(
		Xs = []
	;
		Xs = [H | T],
		P(H) + var(x, y),
		fold2(P, T) + var(x, y)
	).

Now that really isn't too painful either on the fingers or on the eye,
while at the same time making it obvious what is going on.

> > if you change the order of
> > hidden arguments between one release of the compiler and the next,
> > then all sorts of libraries will have to be recompiled in order to
> > guarantee that arguments are passed in the right order.
> 
> I was thinking of more radical changes, such as not passing "hidden
> arguments" as arguments at all, but passing them in memory.  This
> would often save a *lot* of argument shuffling.  As soon as you
> document how something like this is implemented, you make it very
> difficult to change the implementation later.

Hmm.  It would be nice if the compiler could automatically spot this
optimization.  In fact, I've a funny idea that mode analysis should
do most of it.  The compiler would need to spot when an argument pair
is used in a single-threaded context and the call graph over which
that argument pair is threaded and then compile that as a global
rather than a passed argument.  Of course, this would require some
careful thought in a concurrent version of Mercury.

Cheers,

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