iterator_item icon indicating copy to clipboard operation
iterator_item copied to clipboard

[question] Is the marker `fn*` justified?

Open robinmoussu opened this issue 2 years ago • 2 comments

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?

robinmoussu avatar Nov 12 '21 07:11 robinmoussu

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?

estebank avatar Nov 12 '21 18:11 estebank

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;
    }
}

robinmoussu avatar Nov 12 '21 19:11 robinmoussu