[mercury-users] New to mercury: structure reuse and efficiency
Ralph Becket
rafe at cs.mu.OZ.AU
Fri Jul 30 10:27:54 AEST 2004
Gregory D. Weber, Thursday, 29 July 2004:
> There are a few of points here that I don't understand.
>
> Peter Schachte writes:
> > Gregory D. Weber wrote:
> >
> > >There's an implied question here which I don't think has been answered:
> > >how _does_ he do what he wants to do here, which, I believe, is to
> > >make sure that H does not equal T before making the recursive call?
> > >
> > > > member([H|T], H).
> > > >
> > > > member([H|T], X) :- not(X=H), member(T, X).
> > >
> > >
> > I think the best answer to your question is: you don't, the compiler
> > does it for you. This definition does what he wants:
> >
> > member([H|T], H).
> Let's call this rule 1 ...
>
> >
> > member([_|T], X) :- member(T, X).
> ... and this rule 2.
[By the way, you have the arguments the wrong way round here.
list__member takes a T as its first parameter and a list(T) as its
second parameter. In my explanation below I use this correct argument
order.]
> >
> >
> > In fact, it's better than what he's asking for, because the compiler
> > does it in a more flexible way than you could do it yourself. The
> > compiler is smart enough to notice a call member(L, X) where L and X
> > are both sure to be ground, and it forces the call to be deterministic
>
> [Ralph: s/deterministic/semi-deterministic/]
>
> > for you,
>
> Am I correct in thinking that you are assuming certain mode declarations,
> such as
>
> :- mode list__member(in, in) is semidet.
> :- mode list__member(out, in) is nondet.
Yes, if you supply only the second mode, but call list__member with
(in, in) arguments, then it will infer the first mode for you. The rule
is this: a non-deterministic goal that produces no outputs is
semi-deterministic.
Here's how it works. Say you write
list__member(X, Ys)
the compiler will turn this into the quantified goal
some [V1, V2] (
list__member(V1, V2),
V1 = X,
V2 = Ys
)
(where V1 and V2 are fresh variables) and then reorder the goals as
necessary to ensure that variable bindings are established before the
point they are needed. So if both X and Ys are ground, but we have only
declared the mode
:- mode list__member(out, in) is nondet.
then the compiler will turn the quantified goal into
some [V1, V2] (
V2 = Ys, % Assignment to V2 (*)
list__member(V1, V2), % mode (out, in) is nondet
V1 = X % Equality test (*)
)
since X and Ys are ground at the start of this goal and V1 and V2 are
local variables, this goal as a whole has no outputs. So even though it
looks non-deterministic because of the call to list__member, the
compiler is smart enough to work out that it can treat this goal as
semi-deterministic.
That might look a little involved, but in practice programmers very,
very rarely need to understand details at this level. Usually your
intuition will be correct.
[(*) Mercury separates unifications X = Y into four categories:
assignment, where X is free and Y is bound; construction where X is free
and Y is a data constructor term whose arguments are all bound
variables; deconstruction where X is bound and Y is a data constructor
term whose arguments are all free variables; and equality tests.]
> > so you don't try to see if the element is in the tail of the
> > list when you've already seen that it matches the head.
>
> Why would I (= the member process?) already have seen that it matches
> the head?
No. Does my explanation above clear this up for you?
> In an earlier message, Ralph said the compiler would freely
> rearrange code, but "already" implies that it orders rule 1 before
> rule 2.
No, it doesn't. You can't rely upon disjunctions being evaluated in any
particular order. If this is *necessary*, use an if-then-else.
Otherwise write code where the "ordering" of disjuncts is irrelevant.
> Do we really know this? If we do, is it because, as a
> general principle, the compiler will always arrange to try a base case
> rule before a recursive step rule?
No, you're thinking the "clever" part happens inside list__member. It
doesn't: the "clever" part happens because of the code transformation I
illustrated above.
> > However, when X
> > is unbound, then it will match every element of the list, so you don't
> > want to commit yourself to the first match. Consider this predicate to
> > determine whether two lists have any elements in common:
> >
> > overlap(L1, L2) :- member(L1, X), member(L2, X).
> >
> > Here the first call to member will have X unbound and the second will
> > have it bound, so the first must not commit to the first match (because
> > it may not be the first elment of L1 that appears on L2), but the second
> > one can (because we don't care how many times an element of L1 appears
> > on L2).
>
> Again, if the compiler is free to rearrange code, isn't it possible
> it tries member(L2, X) first?
Absolutely, but it doesn't matter which way the compiler chooses.
Either the compiler generates
:- mode overlap(in, in) is semidet.
overlap(L1, L2) :-
some [X] (
some [V1, V2] (
V2 = L1, % Assignment to V2
member(V1, V2), % mode(out, in) is nondet
V1 = X % Assignment to X
), % This whole subgoal is nondet (it has an output, X)
some [V3, V4] (
V4 = L2, % Assignment to V4,
member(V3, V4), % mode(out, in) is nondet
V3 = X % Equality test
) % This whole subgoal is semidet (it has no outputs)
). % This whole subgoal is semidet (it has no outputs)
or...
:- mode overlap(in, in) is semidet.
overlap(L1, L2) :-
some [X] (
some [V1, V2] (
V2 = L2, % Assignment to V2
member(V1, V2), % mode(out, in) is nondet
V1 = X % Assignment to X
), % This whole subgoal is nondet (it has an output, X)
some [V3, V4] (
V4 = L1, % Assignment to V4,
member(V3, V4), % mode(out, in) is nondet
V3 = X % Equality test
) % This whole subgoal is semidet (it has no outputs)
). % This whole subgoal is semidet (it has no outputs)
Lo, we get the same result.
> > But further, once we find an element in common, we can commit
> > to that, because we don't care which elements are in common, just that
> > at least one is. The Mercury compiler will do all that for you without
> > you having to say anything.
>
> Except for the mode declarations? :-)
You could ask the compiler to infer modes for you, but then it would
construct a separate mode for each different way that member is called.
And the error messages the compiler might generate would be much less
helpful.
Trust us: type and mode declarations are your friends!
-- Ralph
--------------------------------------------------------------------------
mercury-users mailing list
post: mercury-users at cs.mu.oz.au
administrative address: owner-mercury-users at cs.mu.oz.au
unsubscribe: Address: mercury-users-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-users-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the users
mailing list