[question] Is the marker `fn*` justified?
I don't understand why a specific marker (like the star next to fn) would be needed? I don't think that it was correctly justified.
Bjarne Stroustroup, creator of C++, said that for every new features, people wants extra marker, but once they are used to it, they want it removed. It's why there is template<typename T> void foo(t T) in C++ and not void foo<T>(t T)> in C++.
I also don't understand why a marker need to be in the public part of the API. Does this means that if at one point I want to replace a manually written iterator by a generator (or vice-versa) this is a breaking change even if it should only be an implementation detail?
Is it necessary? No. Because of async, there's an expectation that people scan the fn head to identify what the function does. If we only rely on a contextual keyword that appears after the parameters, where the return type might be, people could miss it. It might not be a real problem, of course.
Having said that, the proposed syntax is just one example and is not my preferred version (although it did grow on me while using it). I desire to have a dozen or more alternatives to try out.
What I don't think would be a good idea is having no affordance in the signature, because then changes to the body could cause breaking changes to the callers.
Having redundancy in the syntax can also help: if you want a generator that yields (), with fn* you wouldn't have to write it, like with return types. Redundancy also helps with parsing, but I am not opposed to having a single element in the signature that signals it's a generator and not just a function.
fn* is borrowed from JavaScript. It doesn't mean we need to take it wholesale, but there is precedent for that syntax.
Finally, the "nice thing" about fn* is that it doesn't add a new keyword. Whether that's a consideration even worrying about, particularly given we have k#keywords now, is a separate discussion that I do want to have.
Does this summary of my thinking address your concerns?
Does this summary of my thinking address your concerns?
Not exactly, but it’s because I didn’t expressed my thought with enough details.
Why would this not work?
fn foo() -> impl Iterator<Item=i32> {
gen {
yield 0;
for i in 5..10 {
yield i;
}
}
}
As far as I can tell, using a generator to create an iterator is an implementation detail. I don’t see why making it explicit in the function signature (fn* instead of fn) would be beneficial. Is there any difference for the caller if foo is implemented with a generator, or with a manual implementation of Iterator?
For the second marker (-> yield i32 { … }), I thought that it was just a convenient way of writing -> impl Iterator<Item=i32> { gen { … } }. It there any more fundamental differences?
And finally, if the generator is really an implementation detail, is the top-level gen really needed? If not, what prevents us to write the simpler.
fn foo() -> impl Iterator<Item=i32> {
yield 0;
for i in 5..10 {
yield i;
}
}