[m-rev.] for review: state vars syntax sugar

Julien Fischer juliensf at csse.unimelb.edu.au
Wed Jan 31 01:33:14 AEDT 2007


On Tue, 30 Jan 2007, Peter Schachte wrote:

> Julien Fischer wrote:
>
>>> Touché!  And for the X>>dead mode, doesn't X have to be unique?
>>
>> It makes sense for it to be dead, however the current implementation
>> doesn't complain if it isn't unique (which is almost certainly a bug).
>> Actually, in a funny way, it makes sense to be able to define the mode
>> X >> dead
>> where X is not unique, but not to actually use it anywhere.
>
> I thought 'dead' meant "there are no references to this term anywhere"?  If so,
> you can't know there are no references to it unless you know there are no
> references to it outside the arguments to this call.

Yep, unfortunately the compiler currently won't stop you doing all sorts
of silly things with nonsense modes like that.  (There was a thread
on one of the Mercury lists about such modes a while back.)

>>> If that covers all useful cases, how about this for a syntax:
>>>
>>>  !(X)                 means X>>X, free>>X
>>>  !destructive         means unique>>dead, free>>unique
>>>  !mostly_destructive  means mostly_unique>>mostly_dead,
>>> free>>mostly_unique
>>>  !input(X)            means X>>X, X>>X
>>
>> I think !input(X) is badly named.  After all !input(free) is really just
>> (unused, unused).
>
> You know, I really can't imagine when you'd ever use !input(X), so drop it.  I
> was worried about trying to make this comprehensive, but it really doesn't have
> to be.  It's just syntactic sugar.  If you want (in, in), just write it.

Going back to the above proposal (and since we seem to be optimising for
the common case), there's a fairly good argument for making !(X) mean
unique >> dead, free >> unique given that probably the most common use
for state varaibles is for threading the I/O state.

> > I have a few problems with the inst based proposal as it stands:
>>
>> (1) it has a much higher syntatic overhead than the mode based one
>>     in which I only had to remember one piece of syntax; I now have
>>     to remember five or six new pieces.
>
> I don't think it's quite that bad.  Most of it is pretty intuitive after you
> get the basic idea.  The conceptual shift is that you think of a state variable
> !Var as specifying a value that changes throughout the execution of the clause,
> rather than as a different pair of variables every place it appears, with the
> variables threaded together in a certain way.  Then !.Var denotes the current
> value, and !:Var specifies a new value, for that state variable.

I don't think of state variables that way: they are a sequence of values,
the funny notation with the `!'s is just so we don't have to go the trouble
of consistently labelling them all.

> With that view, it makes sense for it to have a single type and a single,
> consistent instantiation state (rather than the confusion of 4 instantiation
> states).

Even leaving uniqueness aside I don't see that there needs to be a single
instantiation state.  It is perfectly reasonable for subtype information
to be change through a sequence of operations (and the type to remain
unchanged).

> There is a catch with uniqueness that the final inst of the input
> must be dead, requiring a special case.  But let's get rid of the !destructive
> notation I specified before and just make !unique and !mostly_unique "do the
> right thing" by forcing the final inst of the input to be dead.  And while I'm
> simplifying, let's drop the !backwards(Inst), because I really doubt it'll ever
> be used.  With those changes, and dropping !input(Inst), there's only !Type,
> !Inst, and !(Type::Inst).  Now I think it's conceptually simpler than the other
> proposal.

I don't - see below.

>> (3) I cannot use the inst based syntax to encode a change of uniqueness,
>>     which is trivial to do with the mode based one, e.g.
>>
>>   :- pred do_complex_op_and_make_unique(!foo::(in, uo)) is det.
>>
>>   do_complex_op_and_make_unique(!Foo) :-
>>     op_part1(!Foo),
>>     ...
>>     op_partn(!Foo),
>>     make_unique(!Foo),
>>     do_more_stuff(!Foo).
>
> That's a feature, not a bug.  You also might want to change *types* in a goal
> (eg, from an association list to a map), and my syntax doesn't allow that
> either.  I note that your preferred syntax inconsistently allows the inst to
> change, but not the type.

That's not inconsistent, it's just more expressive :-)  In any case, if I
wanted to change the type I would just have written two types in the first
place; the proposed syntax doesn't buy me anything there.

> But if a state variable changes type or
> instantiation state, then it can't just be called anywhere.

Which would be the point.  You could use type or instantiation changes
(particularly of the subtype variety) to ensure that operations are carried
out in a particular order and that the compiler picks up any attempt to 
change this on the part of the programmer.

> You divide preds that use that state variable into three classes:  those
> that use the first type or inst, those that use the second, and those that
> transform from one to the other.  I think you're much better off *not*
> using state variable notation in such cases, at least not for the
> predicates that do the transformation.

> So let's compare the proposals.  The current proposal, as I understand it:
>
> 	pred declarations:	!type specifies types of two arguments
> 	mode declarations:	no support; must specify two modes

Not quite, you can specify (mode, mode) in the list of modes but it is just
flattened out.

> 	predmode declarns:	!Type::(Mode, Mode)

To which you can add:

  	typeclass method decls of various sorts - as above
  	lambda expressions
  	mode-specific clauses
  	foreign_export pragmas - maybe

> 	Note that nothing stops you from writing, eg:
>
> 		:- pred append(!list(T), list(T)).
> 	    or	:- pred append(!list(T)::(in, in), list(T)::out).

In that case the difficulty of writing the corresponding clauses
for append with that particular arrangment of state variables might
soon give me pause for thought.  e.g.

  	append(!I Zs) :-
  		(
  			!.I = [],
  			Zs = !:I
  		;
  			!.I = [ X | Xs0],
  			append_3(Xs0, !:I, Zs0),
  			Zs = [ X | Zs0]
  		).

> My updated proposal:
>
> 	pred declarations:	!type specifies type for a state variable
> 	mode declarations:	!inst specifies instantiation of a state var
> 	predmode declarns:	!(type::inst) specifies type and inst for
> 				state variable

Previously (and in the current proposal), :: is always followed by a mode
or a 2-tuple of modes, with your proposal I now have to switch my
thinking between modes and insts depending on whether the argument I'm
looking at is a state variable pair or not

> If you find it annoying, as I do, that pred and mode declarations specify two
> types and modes for a predicate when the call looks to only have one, then I
> think my proposal works better.

It only looks like one argument if you fail to see the `!' in front of it.

> If, OTOH, you're just looking for a way to
> save yourself from having to repeat type specifications over and over in pred
> and predmode declarations (that's the only benefit I see from the current
> proposal),

The current proposal wasn't just restricted to just pred and predmode
decls - see discussion of lambda expressions below.

> then the following 2 changes, taken together, are more widely
> applicable and seem simpler than the current proposal:
>
> 	In a predmode declaration, allow Type::(Inst1,... Instn) as an 		abbreviation
> for Type::Inst1, ... Type::Instn.   Eg, :- pred append(list(T)::(in, in, out)).
>
>
> 	In a pred declaration, allow N * Type, where N is an integer, as an
> abbreviation for N repetitions of Type.  Eg:  :- pred append(3 * list(T)).

Actually, what I'm looking for is way of simplifying the state variables
in the heads of labmda expressions, e.g.


  	P = (pred(X::in, !IO::(di, uo)) is det :-
  		...
  	)

as opposed to:

  	P = (pred(X::in, !.IO::di, !:IO::uo) is det :-
  		...
  	)

It was just that the initial discussion was in terms of pred/mode/predmode
declarations.  That said I think whatever syntax is chosen for
pred/mode/predmode declarations should work for lamda expressions as well
(and mode specific clauses too).

Julien.


More information about the reviews mailing list