iterator_item icon indicating copy to clipboard operation
iterator_item copied to clipboard

Alternative syntax suggestion: `=>` for yield type

Open rolandsteiner opened this issue 3 years ago • 3 comments

I have toyed with generator syntax myself, and my favorite syntax would be to use => for the yield type, which IMHO is very readable and succinct, using the mnemonic "one-lined arrow for the final return that happens once, two (i.e., multi)-lined arrow for yields that may happen more than once/multiple times". This syntax composes quite nicely with -> for the return type, and with async streams. Using the type names from the Generator trait:

fn(...) -> Return { ... }                 // regular function
fn(...) => Yield { ... }                  // generator function
fn(...) => Yield -> Return { ... }        // generator function that has a non-() final return

async fn(...) -> Return { ... }           // regular async function
async fn(...) => Yield { ... }            // async stream
async fn(...) => Yield -> Return { ... }  // async stream, returning non-() final return

|...| => Yield -> Return { ... }          // generator closure
async | ... | => Yield -> Return { ... }  // async stream closure

Arguably, that the difference between => and -> could be hard to spot on a glance, and a separate keyword in front, in order to indicate that the body of the function is re-written into a state machine, would be preferable. In this case I would argue for the commonly proposed yield fn syntax, but still with using => to indicate the yield type:

fn(...) -> Return { ... }                       // regular function
yield fn(...) => Yield { ... }                  // generator function
yield fn(...) => Yield -> Return { ... }        // generator function that has a non-() final return

async fn(...) -> Return { ... }                 // regular async function
async yield fn(...) => Yield { ... }            // async stream
async yield fn(...) => Yield -> Return { ... }  // async stream, returning non-() final return``` 

yield | ... | => Yield -> Return { ... }        // generator closure
async yield | ... | => Yield -> Return { ... }  // async stream closure

Finally, one could shoe-horn the semicoroutine resumption type R in here as well if required. My suggestion for this would be:

yield fn (...) => Yield <= | R1, R2, ... | -> Return { ... }  // R1, R2, ... being members of the resumption tuple R

rolandsteiner avatar Nov 15 '21 13:11 rolandsteiner

I like the way you've presented things and make a good argument for it. I am slightly concerned with users drawing an incorrect relationship between generators and match arms, but other than that it does make sense. I'll try implementing it.

estebank avatar Nov 16 '21 17:11 estebank

Just to put my two cents here, I really like this syntax! In fact, I was actually on my way to submit the exact same suggestion until I found this gem already here. The fact that we've (independently) arrived at the same syntax attests to how intuitive and ergonomic it is.

am slightly concerned with users drawing an incorrect relationship between generators and match arms.

Instead of using =>, perhaps we should also consider an alternate "arrow notation" such as *>. This was mainly inspired by JavaScript’s function* syntax and the fn* syntax you proposed in your blog post.

Just like @rolandsteiner's original formulation, the *> may also serve as a mnemonic for implying "multiple yielded values"—in a similar spirit to wildcard operators (e.g. Bash shell expansions and Regex's zero-or-more-matches).

fn func();
fn func_with_value() -> Return;
fn iterator() *> Yield;
fn state_machine() *> Yield -> Return;

async fn future();
async fn future_with_value() -> Return;
async fn stream() *> Yield;
async fn state_machine() *> Yield -> Return;

I would love to know your thoughts!

BastiDood avatar Feb 03 '22 20:02 BastiDood

What if yield would take a type parameter?

yield(i32) fn (...) -> Return { }
  • yield could be a special case for yield(())
  • We somewhat have precedence for this syntax in the form of pub(crate)
  • Typing yield my_var; would closely align with the fact that my_var needs to be of i32 (in the above case)
  • if we wanted to be closer to type parameters, we could consider yield<i32>
  • It would compose well with async: async yield(i32) fn stream() { }

thomaseizinger avatar Feb 11 '22 05:02 thomaseizinger