There's not question in my mind that it should behave the same as throwing function calls with inout
parameters: setters are run when an exception is thrown in such a case; so setters must be run when yield
fails too. I think it'd be problematic if yield
behaved any differently.
To be clear, all the code you see in the generator would run as written until the end of the function. If you have yield &a
it'll run the getter and setter of &a
(or _modify
) even if there's no one to yield to anymore, just as if you were passing it inout
to a function with an empty body. There is really no error path in the generator (unless you write one explicitly), it just runs it course.
While maybe not optimal, the generator should behave correctly even if you don't give a single though about errors, and that's the point.
The last example of my last post illustrates how good a non-failing yield
is at expressing the control flow. Here's the equivalent rewrite using defer
assuming a yield
that exits the generator on failure:
func generate() {
do {
var (a, b) = fetchAB()
defer { commitAB(a, b) }
yield &a
yield &b
}
do {
var (c, d) = fetchCD()
defer { commitCD(c, d) }
yield &c
yield &d
}
}
The problem is it's likely you'll forget to put the commit calls in a defer
with a do
block for correctly scoping those defer
. I said "likely you'll forget", but I should have said you'll definitely forget it you aren't mindful of the error path while writing the code.
Using finally
makes the flow a bit more linear, but doesn't really help to remind you you need to write all this control flow in the first place:
func generate() {
do {
var (a, b) = fetchAB()
yield &a
yield &b
} finally {
commitAB(a, b)
}
do {
var (c, d) = fetchCD()
yield &c
yield &d
} finally {
commitCD(c, d)
}
}
I didn't want to write an example using yield β¦ else
because it's just awful having to duplicate those commit calls. That said, the obligatory else
would be a good reminder of the error path. People generally want to focus on what they want the code to do when writing code, not error handling and they might see the compiler complaining as an annoyance you need to silence by writing an empty else. If you do that you end up with the same problem as above, but at least you were warned I guess.
I think it's far better if we can just get rid of the error paths when they're not needed.