RustbotPython icon indicating copy to clipboard operation
RustbotPython copied to clipboard

Consider formatting Iterator specially in `?eval`?

Open kangalio opened this issue 5 years ago • 2 comments

The situation often comes up that you want to demonstrate some iterator chain using ?eval. Directly writing the iterator yields bad results: Screenshot_20210128_113723

The obvious workaround is to collect into a vector: Screenshot_20210128_113756

However, this workaround is slightly problematic:

  • it adds boilerplate
  • it may confuse beginners, particularly with the heavy syntax (::<Vec<_>>)
  • it encourages beginners to overuse vector allocations instead of iterating iterators directly

Perhaps there is a way to special-case the return value formatting for iterators? In the above example, the bot output could look like this:

1, 3, 5, 7, 9

Advantages:

  • Code is simple to write, easy to read, pretty
  • Output is very pleasant to look at
  • No boilerplate for users
  • Doesn't lead beginners to over use vector allocations

Potential difficulties:

  • Tricky to implement. Requires either a hackish display trait with a specialization for iterators; OR static analysis to determine te type of the return value. Both are not great
  • Introduces more "magic" to the bot. It may be hard to understand for bot users where this formatting is coming from
  • What to do on long or endless iterators? (Probably best to just render an ellipsis after a certain number of items)

kangalio avatar Jan 28 '21 10:01 kangalio

In hindsight, I'm thinking this is way too much effort and too brittle for little benefit.

kangalio avatar Feb 09 '21 20:02 kangalio

Dtolnay had discovered a clever trick to emulate specialization using autoref. This trick can be applied here to provide a specialization output format for Iterators.

The final generated code looks like this:

struct Wrap<T>(T);

trait Output {
    fn output(self);
}

impl<T: Iterator> Output for Wrap<T> where T::Item: std::fmt::Debug {
    fn output(mut self) {
        if let Some(item) = self.0.next() {
            print!("{:?}", item);
            for item in self.0 {
                print!(", {:?}", item);
            }
            println!();
        }
    }
}

impl<T: std::fmt::Debug> Output for &Wrap<T> {
    fn output(self) {
        println!("{:?}", &self.0);
    }
}

fn main() { Wrap({
    // USER CODE HERE
}).output(); }

This code can presumably be minified considerably. The utility structs and traits might also have to be wrapped in a macro for hygiene, so that the user doesn't accidentally use Output or Wrap in their own code and is confused about the result.

Now, I tested this code with a few types and it yielded the correct results each time. It seems robust enough, so the "tricky to implement" concern from above is dealt with I think.

The "magic" concern is definitely still valid though, in two aspects:

  1. it may be hard to understand for bot users where the special iterator formatting is coming from
  2. users may be confused when the bot redirects them to the playground and they're greeted with a wall of funky glue code
    • this aspect could be alleviated by falling back to the simple old formatting mechanism only when sharing the playground to the user. However, this is another hack on top of a hack. Objectively speaking, it seems viable, though it kinda feels wrong

Remaining questions:

  • is the bot complexity worth it for the convenience?
  • should the bot sneakily remove the complex iterator formatting mechanisms when sharing the generated code to the user?
  • what to do on long or cyclic iterators? Print ... after a certain number of items or a certain number of printed characters?

kangalio avatar Mar 15 '21 10:03 kangalio