There are approximately 549839483286534 interrelated sub-issues currently 
complicating figuring out how we want to treat functions/closures/blocks.

Patrick, Graydon, and I spent some time yesterday tackling the sub-issue of 
dealing with blocks that can affect control flow, for use in iterator functions.

Originally the plan was that all lambda-blocks could trigger break/continue. 
The downside of this is that it means we need to support a block causing a 
break or continue whenever we call one. It also means that blocks would have a 
different calling convention, so to pass a "normal" function to something that 
can also take a block, it needs to be converted. Furthermore, there are a lot 
of places that should be able to take blocks where it doesn't really make sense 
to break and continue (map, for instance: what does map return if the block 
returns early?).

The proposal we discussed looked something like:
Make the flow-controlling return value of an iterator-body-block explicit by 
introducing a new type "flow_control" that represents the flow-controlling 
implicit return value of the iterator-body-block. Then, the type of a 
vec_for_each function would be something like "fn[T](&fn(&T) -> flow_control, 
&T[])".
And then we require that you can call only a function that returns flow_control 
from within a loop. (We could also reify flow_control even more, so that you 
can actually get and create and inspect flow_control values, and then have some 
explicit operation to apply the result of a flow_control value.)

A little trickier is if we want to allow a iterator-body-block to force a 
return of the function that called the iterator. This is useful but a little 
terrifying. It requires that the flow_control value be passed through the 
iterator function and then interpreted by the calling function. If we want to 
be able to force the return of some value, then we would probably need to make 
flow_control parameterized, giving flow_control[T]. Furthermore, you want the 
iterator function to propagate the return flow_control but you just want the 
calling function to return without forcing /its/ caller to return... You could 
do something where you inspect the return types of the function and use that to 
figure out what to do, but. I think trying to do that while keeping handling of 
the flow_control value completely implicit gets pretty hairy pretty fast.

Also note that one downside of making explicit the flow_control type is that 
you can no longer directly pass 

----------------------------------------------------------------
I have an extension to that scheme that I think fixes up some of these issues. 
My idea is to take the idea of reifying flow_control to its extreme, getting 
rid of most of the special cases.

We could make flow_control an ordinary tag type and then have convenience 
macros that return the tags as well as a macro that interprets a flow_control 
value and applies it. (All of the things I say are macros could also just be 
language features.)

so we could do something like:
tag flow_control[T] {
    f_normal;
    f_break;
    f_continue;
    f_return(T);
}

And then we have some simple macros to return the different flow_control 
values, so that #return means "ret f_return" (or don't bother and do it 
explicitly).

The actual control flow would be handled by some macros (I'm not sure what our 
macro syntax will be like, so I'm making things up):
The #call_body macro, which is for calling the body of an iterator. "#call_body 
e" would expand to:
alt (e) {
  f_normal { }
  f_break { break; }
  f_continue { continue; }
  f_return(?x) { return f_return(x); } // propagate the return to the caller
}
And the #iter macro, which is for calling an iterator function if you want to 
allow the block to trigger a return. "#iter e" would expand to:
alt (e) {
  f_normal { }
  f_break { break; }
  f_continue { continue; }
  f_return(?x) { return x; }
}

A full example of this scheme in action:
fn for_each[T,S](&T[] v, &fn(&T) -> flow_control[S] f) -> flow_control[S] {
    for each (x in v) {
        #call_body f(x);
    }
    ret f_normal;
}

fn contains[T](&T[] v, &T x) -> bool {
    #iter for_each(v, {|y|
        if (x == y) { #return true; }
        ret f_normal;
    });
    ret false;
}

I think this is mostly very clean and unburdensome, except for the "ret 
f_normal"s that are required at the end of all bodies, which is an enormous 
burden. I have two possibilities to solve that:
1) The "more principled" solution is to introduce a macro #body such that #body 
{|x1|...|xn| code} becomes {|x1|...|xn| { code } ret f_normal;}. contains then 
becomes:
fn contains[T](&T[] v, &T x) -> bool {
    #iter for_each(v, #body {|y|
        if (x == y) { #return true; }
    });
    ret false;
}

2) Alternately, we could make it so that there is an implicit "ret f_normal" at 
the end of every function that returns flow_control, in the same way that 
(notionally) there is an implicit "ret ()" at the end of every function that 
returns ().

I like this solution because I think it effectively captures a really handy use 
of blocks without needing add much "magic" or special cases. In particular, I 
think that making it explicit gives us a good method for forcing the return of 
a value from an iterator body block whereas I don't think we had a good way to 
do that when keeping the control flow handling implicit. It also has the 
advantage that the flow_control value can be inspected and handled in custom 
ways (perhaps there is some cleanup that needs to be done before returning).

------------------------------------------------------------------

What are people's thoughts?

-sully
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to