golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.3k stars 17.7k forks source link

proposal: new package simpleinput for Go education #54646

Open earthboundkid opened 2 years ago

earthboundkid commented 2 years ago

Problem:

Every few months, someone will post on Reddit that they're trying to make an intro-to-CS-style input prompt and they're confused why their code using fmt.Scan* doesn't work (inevitably because the input has a space and they're ignoring errors). I think that just for the use case of education, there should be something that does the thing that they want.

Here are some typical posts * https://www.reddit.com/r/golang/comments/alvdhl/force_golang_to_scan_a_number_instead_of_a_single/ * https://www.reddit.com/r/golang/comments/atlehx/flushing_standard_input_before_scanf/ * https://www.reddit.com/r/golang/comments/2h5qnw/using_numerical_values_from_slices/ * https://www.reddit.com/r/golang/comments/wv1i2z/can_someone_tell_me_what_im_doing_wrong_im_trying/ * https://www.reddit.com/r/golang/comments/hkz1im/what_is_the_most_idiomatic_way_of_getting_input/ * https://www.reddit.com/r/golang/comments/8umvpt/unclear_on_scan/ * https://www.reddit.com/r/golang/comments/4hktbe/read_user_input_until_he_press_ctrlc/ * https://www.reddit.com/r/golang/comments/tq3wpc/scanln_and_scanf_jump_after_entering_string_value/ * https://www.reddit.com/r/golang/comments/qja60y/issues_with_fmtscanf_reading/ * https://www.reddit.com/r/golang/comments/kee82p/i_am_bit_confused_about_golangs_scanf/ * https://www.reddit.com/r/golang/comments/bkqos6/differences_between_scan_functions/ * https://www.reddit.com/r/golang/comments/whfi8p/question_about_fmtscan/ * https://www.reddit.com/r/golang/comments/p2tnio/in_golang_is_there_is_any_way_to_take_input/ * https://www.reddit.com/r/golang/comments/s85xxl/having_trouble_with_scanning_input_new_to_language/ You can find more by searching site:https://reddit.com/r/golang fmt.scan.

New programmers aren't a huge source of new Go users. On the other hand, even if most Go programmers come from other languages, it is convenient when learning the language to be able to write simple intro-to-CS-style question/response programs to get a feel for the language. There ought to be something very simple that novice can find without knowing anything about Go or IO. Obviously, novices could and probably should be using bufio.Scanner already, but they aren't finding it today. Instead they look in fmt (because they are already using fmt.Println) and find the Scan family of functions. Then they are frustrated when it does not work the way they expect.

Proposed solutions

Proposed solution one is simple: mark the Scan functions as deprecated to hide them from the documentation. Even Rob Pike regrets adding the Scan functions.

Proposed solution two is to add a new package just for education purposes.

// Package simpleinput provides function for basic reading from os.Stdin.
// Functions in this package are concurrent safe. 
// For better performance, use bufio directly.
package simpleinput

// SetSource sets the source that simpleinput buffers reads from. 
// By default, it uses os.Stdin.
func SetSource(io.Reader)

// Source returns the underlying io.Reader that simpleinput buffers reads from.
func Source() io.Reader

// Error returns the last error encountered by simpleinput, if any.
func Error() error

// Read reads from Source until it receives an error, typically io.EOF.
func Read() string

// ReadLine reads one line from Source. It removes "\n" and "\r\n" from its output.
func ReadLine() string

// ReadLines reads Source until it encounters an error.
// It removes "\n" and "\r\n" from its output.
func ReadLines() []string

Obviously, none of this is strictly necessary, but it would help Go learners, particularly those new to programming.

Cf. #54620


Edit: Proposed API is now:

// Package simpleinput provides function for basic reading from os.Stdin.
// Functions in this package are concurrent safe. 
// For better performance, use bufio directly.
package simpleinput

// SetSource sets the source that simpleinput buffers reads from. 
// By default, it uses os.Stdin.
func SetSource(io.Reader)

// Source returns the underlying io.Reader that simpleinput buffers reads from.
func Source() io.Reader

// Read reads from Source until it receives an error. 
// If the error is io.EOF, it returns a nil error.
func Read() (string, error)

// ReadLine reads one line from Source. It removes "\n" and "\r\n" from its output.
// If it encounters an io.EOF, it returns the error.
func ReadLine() (string, error)

// ReadLines reads Source until it encounters an error.
// It removes "\n" and "\r\n" from its output.
// If the error is io.EOF, it returns a nil error.
func ReadLines() ([]string, error)
mvdan commented 2 years ago

How about https://go.dev/doc/faq#x_in_std? Suggesting better fmt scanning APIs is one thing, but if these APIs are primarly for new Go users, perhaps they should be in a Go module that's aimed and promoted to them.

At the same time, I would be conflicted with APIs targetting new users only. If I was learning a language, I would want to learn the "normal" or "proper" way to accomplish a task, not an oversimplified way that I should probably un-learn later.

earthboundkid commented 2 years ago

I think it's a fair question whether Go wants to target new users or just say that's out of scope. However, if the decision is made that targeting new users is in scope, then I don't think it makes any sense to use an external module. You can already find tons of simple input packages online. The forums are full of people who haven't found those modules because they are looking in the standard library for the equivalent of Python's input() and then they get burned using fmt.Scanln instead of what they really want.

mvdan commented 2 years ago

I don't think it would be fair to say Go doesn't target new users. It has grown in users at a very fast pace for ten years now :)

It is true that new users will tend to look at the standard library first, but ultimately they will follow the advice of the documentation they are reading. There are plenty of "blessed" modules outside of the standard library, such as x/crypto and x/net, and they don't need to be in the standard library either. If there was a module like x/guardrails or somesuch, I'm sure that pages like tour.golang.org could point to it or show examples with it.

earthboundkid commented 2 years ago

The question of why should this be in the standard library seems really straightforward to me. A simpleinput package really only has value in the standard library. The point is to make learning Go easier, and as soon as modules are involved, it's not easier.

If I was learning a language, I would want to learn the "normal" or "proper" way to accomplish a task, not an oversimplified way that I should probably un-learn later.

In Python, practically all learning material uses print() and input(), neither of which did I ever use in my years of programming Python professionally. It's normal to have an inefficient "easy mode" for beginners. Learning styles vary and some people want to learn the "real" way to do things earlier and some people prefer to just get things working and fill in the details later. Neither is better than the other, but I think the latter style is more common.

ianlancetaylor commented 2 years ago

If we are going to do this, I think we should be more idiomatic and have Read and friends return (string, error).

apparentlymart commented 2 years ago

After reading this proposal I tried to think about some typical "getting started"-type programs that I might write when learning a new language. It's harder for me to relate to "learning to program" because it's been some time, but I also tried to remember some early programs I wrote.

Here's what came to my mind:

I don't really have a strong opinion about whether it's important to make these easy to achieve, but similar features do seem to help learners of Python as stated above, and Python is typically presented as an example of a good language for beginners. My examples above also lead me to think that the simpleinput API proposed here would be useful for the sorts of problems I can imagine beginners wanting to use to experiment.

I share Ian's sense that it would be better to make these functions follow typical Go error handling patterns, because that seems like an important part of Go's design that I'd want to learn quickly as a beginner, rather than deferring until later. I think a Read (string, error) does still address the stated problem with fmt.Scanf where beginners don't think to check the error, because they'll want to access the string result and therefore won't be able to avoid learning how to handle multiple return values, which will hopefully then encourage learning what to do with that error result. The pattern they'd learn doing that will then set them up for working with almost every fallible API in Go.