Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: fmt: add Printlnf, Fprintlnf, etc #46190

Closed
leighmcculloch opened this issue May 16, 2021 · 19 comments
Closed

proposal: fmt: add Printlnf, Fprintlnf, etc #46190

leighmcculloch opened this issue May 16, 2021 · 19 comments

Comments

@leighmcculloch
Copy link
Contributor

leighmcculloch commented May 16, 2021

Add print functions to fmt that support formatting and append a new line, e.g. Printlnf, Fprintlnf, etc.

The fmt package has these printing functions today:

  • Fprint
  • Fprintf
  • Fprintln
  • Print
  • Printf
  • Println
  • Sprint
  • Sprintf
  • Sprintln

The ln functions are convenient when printing lines to stderr and stdout. The f functions are required for formatting but have no ln equivalent.

It is a pretty common occurrence for me to write or edit code that uses Println and Fprintln, then to have a need to add a formatted parameter to that output. Without fail I rename the function to Printf and forget to include a \n at the end.

If f variants are added for all the ln functions, there would be functions Printlnf, Fprintlnf, etc.

The intended uses of the existing functions would become:

  • Print - Print, no new line appended, no formatting.
  • Printf - Print, with formatting.
  • Println - Print, new line appended.
  • Printlnf - Print, with formatting, new line appended.

A prototype implementation is available at https://4d63.com/fmt.

Example:

func Printlnf(format string, a ...interface{}) (n int, err error)
func Fprintlnf(format string, a ...interface{}) (n int, err error)
code := 400
message := "error message"

fmt.Fprintlnf(os.Stderr, "error: code %d, %s", code,  message)
fmt.Fprintln(os.Stderr, "resending request")
// Output:
// error: code 400, error message
// resending request

It may also make sense for consistency to add the same variation of functions for each scan function in the fmt package.

Proposal template:

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    Experienced

  • What other languages do you have experience with?
    Java, Ruby, C#, C, JavaScript

  • Would this change make Go easier or harder to learn, and why?
    A little easier.

  • Has this idea, or one like it, been proposed before?
    Yes, We should have fmt.Printfln series of functions in package #31214.

    • If so, how does this proposal differ?
      It's identical. The author abandoned their proposal.
  • Who does this proposal help, and why?
    Anyone printing lines.

  • What is the proposed change?
    See above.

    • Please describe as precisely as possible the change to the language.
      See above.

    • What would change in the language spec?
      Nothing.

    • Please also describe the change informally, as in a class teaching Go.
      See above.

  • Is this change backward compatible?
    Yes

  • Show example code before and after the change.
    See above.

  • What is the cost of this proposal? (Every language change has a cost).

    • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
      None.
    • What is the compile time cost?
      Little.
    • What is the run time cost?
      None.
  • Can you describe a possible implementation?
    See above.

  • How would the language spec change?
    It wouldn't.

  • Orthogonality: how does this change interact or overlap with existing features?
    It is modeled after existing print functions in fmt package.

  • Is the goal of this change a performance improvement?
    No.

    • If so, what quantifiable improvement should we expect?
    • How would we measure it?
  • Does this affect error handling?
    No.

  • Is this about generics?
    No.

@gopherbot gopherbot added this to the Proposal milestone May 16, 2021
@mvdan
Copy link
Member

mvdan commented May 16, 2021

My intuition is that you win very little by adding more variants, and this will be very confusing to people trying to read code - particularly those new to Go. It's not that hard to write fmt.Fprintf("%x\n", foo) rather than fmt.Fprintlnf("%x", foo). And you get used to the \n suffix.

@leighmcculloch
Copy link
Contributor Author

And you get used to the \n suffix.

This is what I thought would happen for me, but it hasn't. I still forget to add \n when using the f variations, especially when changing code from using Println to Printf to add formatting.

this will be very confusing to people trying to read code

Could you expand on why this will be very confusing?

@mvdan
Copy link
Member

mvdan commented May 16, 2021

I still forget to add \n when using the f variations, especially when changing code from using Println to Printf to add formatting.

Mistakes can happen. I imagine you're just as likely to forget to add ln than you are to forget to add \n, though.

Could you expand on why this will be very confusing?

More options to choose from, mainly. Especially since all these func names vary only by one or two characters. We already have quite a lot of variants, and they exist for a good reason. More variants when it's just a very minor convenience doesn't seem worth it to me.

@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) May 17, 2021
@adonovan
Copy link
Member

I don't like this idea. The Println variant differs from Print and Printf in two ways, one obvious (the newline) and one subtle: its treatment of arguments that are strings with leading or trailing whitespace. For example:

https://play.golang.org/p/aLlMHWQbCkC

fmt.Sprint(" a ", " b ") == " a  b " // two spaces
fmt.Sprintln(" a ", " b ") == " a   b \n" // three spaces

What does the lnf variant do? I cannot guess, and nor should I have to.

@leighmcculloch
Copy link
Contributor Author

leighmcculloch commented May 17, 2021

It might make it clearer if the function was named fln, or Printfln, so it's clearer that this is a Printf function with a new line appended.

@adonovan Would that make the fln variant clearer?

What does the lnf variant do?

The f variant does not add spaces because it receives only a single string for formatting, so the fln variant should not either.

@adonovan
Copy link
Member

@adonovan Would that make the fln variant clearer?

No. The problem is that behavior of lnf (or fln) is not the composition f and ln. It is yet another thing readers must commit to memory.

@rsc
Copy link
Contributor

rsc commented May 19, 2021

#46190 (comment) is the major reason not to do this. Println is not Print+\n. It changes other things that make no sense to change in Printf.

@magical
Copy link
Contributor

magical commented May 19, 2021

@rsc @adonovan If we think of Printf as Print(Sprintf(...)) and Printlnf as Println(Sprintf(...)) then there's no confusion. Print(ln)'s weird behaviour with multiple arguments doesn't apply, and the only relevant difference is the fact that Println appends a newline. I don't think that alone is sufficient reason to reject this proposal.

I'm somewhat in favor of this proposal. Like @leighmcculloch, I often forget \n in my format strings, particularly when converting a Println call to a Printf call. Having Printlnf available would probably help alleviate that. However, i'm not sure if the minor benefits are worth the new API.

@rsc rsc moved this from Incoming to Active in Proposals (old) May 19, 2021
@rsc
Copy link
Contributor

rsc commented May 19, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@leighmcculloch
Copy link
Contributor Author

i'm not sure if the minor benefits are worth the new API

The main problem this solves is addressing the mistake some of are repeatedly making with missing \n in print calls. To that end, adding a lnf variation for every existing fmt function would probably be unnecessary. I think the proposal could be limited to adding Printlnf and Fprintlnf. Originally I was thinking other functions, such as Sprintlnf, should be added for consistency, but that is a much larger API change. If we limit the proposal to only adding Printlnf and Fprintlnf, the scope of the new API introduced is much smaller and focused on the problem.

@bvisness
Copy link

I too sometimes forget to put \n in my format strings, but those mistakes are always balanced out by having too many newlines (using Println when I should have used Print for example). I always end up gravitating back to Printf for everything, and if I make a couple mistakes, oh well.

There are clear reasons in this thread why these proposed functions would be confusing, and the design decisions around Print and Println make it pretty clear that if precise formatting matters to you, you should just be using Printf.

I don't think the inevitable confusion could ever be worth it for a problem as trivial as forgetting a newline.

@rsc
Copy link
Contributor

rsc commented May 26, 2021

@rsc @adonovan If we think of Printf as Print(Sprintf(...)) and Printlnf as Println(Sprintf(...)) then there's no confusion.

But that's not what Println means.

@leighmcculloch
Copy link
Contributor Author

@rsc @adonovan If we think of Printf as Print(Sprintf(...)) and Printlnf as Println(Sprintf(...)) then there's no confusion.

But that's not what Println means.

Did you mean that's not what Printf means? Because @magical only provides definitions for Printf and Printlnf, not Println.

Comparing the definitions of Printf and Sprintf, it does seem like Sprintf is a subset of Printf's behavior.

Printf formats according to a format specifier and writes to standard output.

https://golang.org/pkg/fmt/#Printf

Sprintf formats according to a format specifier and returns the resulting string.

https://golang.org/pkg/fmt/#Sprintf

@bvisness
Copy link

bvisness commented May 26, 2021

Println(Sprintf(...)) exhibits none of the special behavior of Println, so Printlnf would therefore be confusing to anyone who knows how Println works.

@rsc
Copy link
Contributor

rsc commented May 26, 2021

Based on the discussion above, this proposal seems like a likely decline.
— rsc for the proposal review group

@rsc rsc moved this from Active to Likely Decline in Proposals (old) May 26, 2021
@andig
Copy link
Contributor

andig commented May 26, 2021

My intuition is that you win very little by adding more variants, and this will be very confusing to people trying to read code - particularly those new to Go. It's not that hard to write fmt.Fprintf("%x\n", foo) rather than fmt.Fprintlnf("%x", foo). And you get used to the \n suffix.

It is actually hard to write when you‘re on a German keyboard. The backslash is a pain and it would be great not having to use it all the time for simple log statements.

@theckman
Copy link
Contributor

This feels like one of those real problems that can be trivially solved within each project, versus making it an explicit feature of the language and needing to support the API in perpetuity (i.e., until Go 2). If you run into this problem often, you could copy this helper function into all the packages you use that need it. It seems like it would be unlikely to change, and so it feels like a great candidate for "A little copying is better than a little dependency":

func printf(format string, a ...interface{}) (n int, err error) {
    return fmt.Printf(format+"\n", a...)
}

@magical
Copy link
Contributor

magical commented Jun 2, 2021

Println(Sprintf(...)) exhibits none of the special behavior of Println, so Printlnf would therefore be confusing to anyone who knows how Println works.

This argument doesn't make sense to me. We already have Printf and nobody is confused that it doesn't work like Print. Do you expect Printf("%d%d", 1, 2) to insert spaces between the two numbers? No, and you wouldn't expect that from Printlnf either.

@rsc rsc moved this from Likely Decline to Declined in Proposals (old) Jun 2, 2021
@rsc
Copy link
Contributor

rsc commented Jun 2, 2021

No change in consensus, so declined.
— rsc for the proposal review group

@golang golang locked and limited conversation to collaborators Jun 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

No branches or pull requests

9 participants