Closed laumann closed 9 years ago
See the examples/ticket_ordering.rs for an example of using these iteration constructs.
I don't feel like we should merge this. It's added complexity for no clear reason. There's lack of sequencing in Rec, yes, but it doesn't have any effect on the expressiveness of the language, as you said. For example:
IterIn<Send<u8, IterEps>,
Recv<u8, Eps>>
is exactly equivalent to
Rec<Offer<Send<u8, Var<Z>>,
Recv<u8, Eps>>
The only benefit being that it is clear, simply from the name of the construct, that the once we enter second branch of IterIn
we cannot go back to the first. However, the same is equally true in the Rec<Offer
case, as there is no Var<Z>
inside that branch, it's just not immediately apparent from the name of the first construct.
It's also true that after having entered the second branch of IterIn
, we no longer need to keep the whole construct on the stack, but that doesn't make much of a difference, since having entered the second branch of the Rec<Offer
, we'd ignore the stack anyway, and use generics in real code. Thus, the full types become: Chan<E, Recv<u8, Eps>>
in both cases.
Additionally, IterEps
only allows one to iterate back a single level, while Var
allows one to go up one or more levels on the stack.
That said, I think it's a worthwhile experience to have had. We've explored this path, and so far it hasn't turned out to provide us with much added power of expression.
Just to make sure that we agree.
What you are arguing are:
IterIn
/IterOut
can be (mechanically) (re-)written using Rec
. (I find that easy to believe.)All types written with Rec
can be (mechanically) (re-)written using IterIn
/IterOut
.
This is more surprising, thus I'd like to see some argument for that. And I'm not sure I can follow Philip's argument.
What I meant to express was: Any protocol written using Rec
can be rewritten using IterIn
/IterOut
to allow the same sequences of interactions - sends and receives discounting information exchange in choose()
/offer()
and in_while()
/exit_while()
But any given Rec
cannot in general be (mechanically) (re-)written. Specifically in Rec
can enforce infinite recursion, which cannot be expressed in a finite type description using IterIn
/IterOut
. The simplest example is Rec<Var<Z>>
. Attempting to rewrite it as an iterative construct would result in an infinite type: IterX<IterEps, IterX<IterEps, IterX<IterEps, ...>>>
where IterX
means either IterIn
or IterOut
.
This discussion seems to indicate that Rec
s are more expressive and IterIn
/IterOut
don't add anything but syntactical sugar.
EDIT: Add parenthesis
EDIT: Strike-out the claim of some equivalence in expression of Rec
and IterIn
/IterOut
(eating my own words). See the discussion below.
@Munksgaard In response to your earlier comment:
Additionally, IterEps only allows one to iterate back a single level, while Var allows one to go up one or more levels on the stack.
True, but it doesn't prevent nested loops. I'm not sure I see how this is a problem
@kfl Regarding @Munksgaard's argument I think he's referring to the types of the channels once iteration stops. For the IterIn
case it will looks like this
Chan<(), Recv<u8, Eps>>
and for the Rec
case the type will look like this:
Chan<(Offer<Send<u8, Var<Z>>, Recv<u8, Eps>>, ()), Recv<u8, Eps>>
Although the types are different, we can effectively ignore the stack, as the protocol in the second branch does not allow us to recurse.
IIRC rustc
is even helpful here: if we wanted to pass the channel at this point to another function we could write:
fn finish_it<E>(c: Chan<E, Recv<u8, Eps>>) { ... }
and rustc
would infer E
as appropriate (we cannot do the same with the Recv<u8, Eps>
part)
( @Munksgaard please correct me if I'm wrong)
What I'm trying to say is, that sometimes it is a feature to be less expressive. Meaning that Rec
might be too general.
However, now I'm even more confused. Because it seems that Rec
is not expressive enough, by the example you give.
The type:
Chan<(), Recv<u8, Eps>>
is (too me) rather different than:
Chan<(Offer<Send<u8, Var<Z>>, Recv<u8, Eps>>, ()), Recv<u8, Eps>>
The former allows a finite sequence of communications whereas the later allows infinite sequences of communications. It's like saying that the regular expression b*|a
is just as good as the regular expression a
because 'we can effectively ignore the stack, as the protocol regular expression in the second branch does not allow us to recurse iterate'.
What I meant to express was: Any protocol written using
Rec
can be rewritten usingIterIn
/IterOut
to allow the same sequences of interactions - sends and receives discounting information exchange inchoose()
/offer()
andin_while()
/exit_while()
I have had some time to think and I don't agree with this statement anymore:
Rec<Send<u8, Var<Z>>>
only allows an infinite sequence of of 'sends' which cannot be expressed in a (finite) iter construct. For example, IterOut<Send<u8, IterEps>, Eps>
allows an infinite sequence of 'sends', but also allows any finite sequence.
@kfl Chan<(Offer<Send<u8, Var<Z>>, Recv<u8, Eps>>, ()), Recv<u8, Eps>>
cannot recurse anymore - even though the stack contains the Offer
there is no way to get it back (that requires a Var<Z>
)
In terms of the regular expression b*|a
, at the moment you decide to consume a
, there is no way to go back and consume more b
s. The types above reflect the type at that specific point in the protocol, when the choice has been made to choose a branch from which we cannot iterate/recurse (I hope this makes sense :confused:)
What I'm trying to say is, that sometimes it is a feature to be less expressive. Meaning that Rec might be too general.
Right, in particular the iter constructs only allow protocols that can terminate: If we leave out the Rec
and Var
from the session type DSL, the only terminating constructs (in that they don't have a "next" action) are IterEps
and Eps
. Even though we allow IterEps
with the empty environment - Chan<(), IterEps>
- it is not a useful nor "dangerous" channel type (well-typed programs can only drop the value - they cannot interact with the channel in any way). In other words, channel types that can actually be interacted with in well-typed programs must enclose IterEps
in IterIn
or IterOut
, as they are the only constructs that can grow the stack. In case our protocol P
does not contain any iter constructs its protocol must end in Eps
s. If P
contains any iter constructs, we eventually must end with a protocol IterX<Q, R>
where R
is only terminated by Eps
s.
I'm trying to explain it to myself as I go along, so I apologise for any confusion...
I'm closing this, as I think we've established that we don't want this for now.
This adds iteration constructs to the session type "DSL". Specifically three new structs are added with matching dual implementations (phantom data markers omitted):
with a mirror implementation for
IterIn
.IterEps
is its own dual.The idea is that
R
may be repeated zero or more times, where the party with theIterOut
construct decides whether to iterate or not (better names for the structs are welcome).IterOut
allows two functions:where the implementation communicates the decision to the other party (as a boolean).
IterIn
then obtains the decision to iterate or not and must act accordingly.The inner body of the recursive construct (denoted
R
above) must indicate its end usingIterEps
.Semantically, the iteration constructs are equivalent to a
Rec<Choose<P, R>>
construct, but where theRec
struct lacks a notion of a "next" action, the iteration construct provides this as the second type parameter (N
above).Note that there is no gain in expressivity! Any program using
Rec<Choose<P, R>>
can be rewritten to useIterIn
/IterOut
(but not the reverse - see the discussion below), but the lack of sequencing inRec
may be jarring to some users. I would suggest that we only provide one of these constructs though!EDIT: Update based on discussion below