-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: Go 2: string interpolation #34174
Comments
Why can't we use just ${...}? Swift already has one syntax, JavaScript and Kotlin another, C# yet another, also Perl... Why invent one more variation? Wouldn't it be better to stick to the one most readable and already existing? |
Because we may have existing strings with And |
This doesn't seem to have a big advantage over calling |
Yes, it doesn’t besides full type safety, better performance and ease of use. Formatting done right for a language with static typing. |
As far as I can tell this proposal isn't any more type safe than using |
In order to implement this we would have to write, in the language spec, the exact formatting to use for all types. We would have to decide and document how to format a slice, an array, a map. An interface. A channel. This would be a significant addition to the language spec. |
I think it's one of those questions where both ways have the right to exist, and it's just up to the decision makers to decide. In Go, the decision has been already made, quite a long time ago, and it works, and it's idiomatic. That's Historically, there are languages where interpolation inside a string has been present from the very beginning. Notably it's Perl. That's one of the reasons why Perl became so popular, because it was a super-convenient compared to I don't agree with @ianlancetaylor that support of this will need substantial effort. In fact, But I do agree with @ianlancetaylor that using There's one more thing. From my long experience with Perl, where string interpolation was present from the very beginning, I can say that it's not that perfect. There's a problem with that. For simple and trivial programs, and probably for 90% of all programs, string interpolation of variables works just fine. But then, once in a while, you get a But in Go, a different decision has been made. The Introducing proposed syntax will make Go a little more like Swift and/or more like JavaScript, and some may like that. But I think this particular syntax will not make it better, if not a little worse. I think that existing way to print things should stay as it is. And if someone needs more - then there are templates for that. |
If part of the argument here is safety at compile time, I don't agree that's a compelling argument; It's also possible to optimize for performance today via |
Allowing function calls inside interpolated strings would be unfortunate - too easy to miss. |
But type safeness should be guaranteed by the compiler, not by tooling. This is semantics; it is like saying that there should be a tool for verifying where you forgot to null check instead of having optionals or explicitly declared nullable values. Apart from that - the only way for this to be safe is with dependent types. String interpolation is just more syntatic sugar for the same stuff as |
Or perhaps something like modifying the language to just work better with fmt.Printf & friends. Like if fmt supported something like e.g. this code: name := "foo"
age := 123
fmt.Printf("The gopher %(name)v is %(age)2.1f days old.") would really compile to: name := "foo"
age := 123
fmt.Printf("The gopher %(name)v is %(age)2.1f days old.", name, age) And That's a pretty special case language change, though. |
For me, type-checking as a reason to include string interpolation in the language is not that compelling. There is a more important reason, which is not depending on the order in which you write the variables in Let's take one of the examples from the proposal description and write it in Go with and without string interpolation:
name := "Mark"
date := time.Now()
fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
name := "Mark"
date := time.Now()
fmt.Printf("Hello, %{name}! Today is %{date.Weekday()}, it's %02{date.Hour()}:%02{date.Minute()} now.") For me, the second version is easier to read and modify and less error-prone, as we do not depend on the order in which we write the variables. When reading the first version, you need to re-read the line several times to check it is correct, moving your eyes back and forth to create a mental mapping between the position of a "%v" and the place where you need to put the variable. I've been fixing bugs in several applications (not written in Go, but with the same issue) where the database queries had been written with a lot of '?' instead of named parameters ( So, for a language whose goal is to ease maintainability in the long term, I think this reason makes it worth it to consider having string interpolation Regarding the complexity of this: I don't know the internals of Go compiler, so take my opinion with a grain of salt, but Couldn't it just translate the second version showed in the above example to the first one? |
@ianlancetaylor pointed out that my sketch above (#34174 (comment)) isn't strictly backwards compatible, as there might be rare programs where this would change their behavior. A backwards compatible variation would be to add a new type of "formatted string literal", prefixed by, say, an e.g. this code: name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.") would really compile to: name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.", name, age) But then the double |
I also don't understand the inner workings of the compiler, so I (perhaps foolishly) also assume that this could be implemented in the compiler, so that something like would compile to its equivalent current formulation. String interpolation has the obvious benefit of higher readability (A core design decision of Go) and also scales better as the strings that one deals with become longer and more complicated (another core design decision of Go). Please notice also that using {age} gives the string context that it otherwise wouldn't have if you were only skim reading the string (and of course ignoring the type that was specified), the string could have ended "You are tall", "You are [at XXX location]", "You are working too hard" and unless you put in the mental energy to map the format method to each instance of interpolation it isn't immediately obvious what should go there. By removing this (admittedly small) mental hurdle the programmer can focus on the logic rather than the code. |
The compiler implements the language spec. The language spec currently says nothing at all about the fmt package. It doesn't have to. You can write large Go programs that don't use the fmt package at all. Adding the fmt package documentation to the language spec would make it noticeably larger, which is another way of saying that it makes the language that much more complex. That doesn't make this proposal impossible to adopt, but it is a large cost, and we need a large benefit to outweigh that cost. Or we need a way to discuss string interpolation without involving the fmt package. This is pretty clear for values of string type, or even |
I'm not in favor of this proposal partly because of what @ianlancetaylor said above and partly because, when you try to interpolate complex expressions with formatting options, any readability advantage tends to go out of the window. Also it's sometimes forgotten that the ability to include variadic arguments in the multiplier := 3
message := fmt.Sprint(multiplier, " times 2.5 is ", float64(multiplier) * 2.5)
age := 21
fmt.Println("My age is:", age)
name := "Mark"
date := time.Now()
fmt.Print("Hello, ", name, "! Today is ", date.Weekday(), ", it's ", date.String()[11:16], " now.\n")
name = "foo"
days := 12.312
fmt.Print("The gopher ", name, " is ", fmt.Sprintf("%2.1f", days), " days old\n.") |
Another reason to still add and have it in the language: #34403 (comment) |
We find @alanfo 's comments in #34174 (comment) to be convincing: you can use |
As noted above there is an existing way to approximately do this that even allows for formatting of the individual variables. Therefore, this is a likely decline. Leaving open for four weeks for final comments. |
I'm regularly faced with building text blocks with well over 50 variables to insert. Over 70 in a few cases. This would be easy to maintain with Python's f-strings (similar to C# mentioned above). But I'm handling this in Go instead of Python for several reasons. The initial setup of |
|
I don't know if this is good or not. I think if there is function to get all declared variables like PHP's |
Unlike PHP, Go is a compiled language, so there is no support for anything similar to |
We could add a built in function definedVars() map[string]any that does roughly the same, though. |
The PHP function returns all variables defined anywhere. That is infeasible in Go. |
Yes, but just the variables visible at that point from the current function could be possible. Maybe functionVars() would be a better name then. |
Reading through this thread I had the same thought (less PHP). Getting back to the XY problem, it seems to me that @runeimp biggest frustration is the distance between where a value is specified in a format string (be it fmt.Sprintf, os.Expand, or otherwise) and where a value is provided (I.e. positional parameter or map). I believe the argument that positional parameters introduce cognitive load upon the reader of the code is sound (personally I find it easy to write, but reading such code is a different matter). I'm not clear as to why using a map is difficult. My guess would be that building a set of local variables is more natural than building a map (or struct) or converting one or more structs into a single map (or struct). Then when it comes to producing a new string using your preferred formatting function all the values must restated as either positional arguments, map values or struct fields, which can be tedious and error prone. A (builtin) function that could capture the variables that are currently in scope obviates this construction of local variables into some other form. My proposal would be thus:
If E.g.
More attention would need to be paid to handling package variables and mapping variable names to field names (use a special tag?). I don't know if running user code in the compiler is desirable but a struct method could be called which provides access to local variables from a different scope. E.g.
Where any error or panic results in a compiler error! |
I really like C#s approach for string interpolation.
There are a ton more features: csharp string interpolation formating |
This comment was marked as off-topic.
This comment was marked as off-topic.
Hi guys. It's now 3 (three!) years as I receive updates on this thread in my mailbox. So many, so different, ideas. Many of them are unexpected, many are very smart. So many use cases and situations. It's deep night now, and instead of going home, I read this whole thread, very diligently, from the top down. Why? Because I support my own dialect of Go, and now it's time to finally add this thing there too (as there's little hope in mainstream Go :) So, I thought it'd be good idea to see what people came up with in these three years. Here's the small digest, for those of you reading this far down the thread. Looks like Go as it is now, plus the ideas and packages presented above, ALREADY has all the tools to solve the problem, in several ways. Solution 1:
after this, you can write this code:
Look at that lib, it's has much more to use. And see also the https://src.eruta.nl/beoran/ginterpol. Solution 2:Some genius from Go dev team added os.Expand() into the standard lib. Look at it. Combined with #34174 (comment), and with the shortcuts from Solution 1, you get an excellent Solution 2, ready to use. Solution 3:This is what I am going to implement in my dialect. It's different in that it solves a much broader problem of embedding DSL, it's close in spirit to the https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
Where "IT" and "DSL1" are keys, by which particular DSL handlers are registered by user programmer. Thanks everyone for great analytics and great ideas, and have a good day (and or night)! |
This comment was marked as off-topic.
This comment was marked as off-topic.
My use case for this revolves around writing code generators in both Go and Kotlin. Kotlin has string interpolation, and Go has For a large textual template, I always use In the Kotlin case, there is no need for "text/template" because Kotlin itself can be used to interpolate the template with variable names, type names, etc. I do not re-learn the template formatting language every 6 months when I update the template. My argument is that widespread use of "text/template" in Go and the absence of a need for that library in Kotlin is compelling evidence that string interpolation is useful. -- var code = `var ` + varName + ` = foo()` is pretty close in convenience to var code = `var ${varName} = foo()` Maybe it's not a big enough win for changing the language. However, I think the subtle things matter in programming languages, and I find first class string interpolation in other languages preferable to old school |
Until Go 2.0 does not arrive I have created my own package for string interpolation: https://github.com/evandrojr/string-interpolation Simple string interpolation for golang. Interpolates anything in an easy way. No need to pass the format parameters %d, %s, %t... anymore! It has 3 methods: Print, Println and Sprint: Example:
Output:
|
fmt.Print("Print ", 10, 7, " interpolates anything ", true, 3.4e10) is the equivalent of your code above. |
@DeedleFake To be more precise the equivalent are:
I think it causes confusion the difference of Println and Print since Println always add a space between the arguments and Print adds a space only in some situations. I like my solution since it is deterministic because it never adds spaces between the arguments. I will try to do also:
Output:
I have just asked this question in SO: |
I find @alvaroloes solution close to optimal. This proposed solution is quite elegant. Reduces probability of bugs, and includes formatting capabilities that are quite obvious at first glance. The %{} notation gives a nod to C with the formatting symbols after the % and before {}. If the output of the called function or the variable type doesn't match the given format string type, throw a compile time error. This would enforce type checking while bringing modern interpolation capabilities to the language. I've used Python3 to generate entire project scaffolds (code generation via template strings etc) and right now, I'd never dream of writing a project scaffold generator in Go, perhaps this is for the best in terms of what the language is meant for. Having said that... There are so many aspects of Go that are obviously ergonomic in their design that it almost feels... out of place not to have modern interpolation (yes even if with a historically C-flavor) in the language, it feels like a lacuna for a 21st century programming language. In short I also support @alvaroloes , @alphabettispaghetti , and others' input on this matter. As much as I'm glad that @sirkon opened this issue, and got the conversation going. It will make Go way more productive and attract interesting projects to the language. |
Perhaps it would be useful to consider a simpler approach: #57616 . |
Per the discussion in #57616 this is a likely decline. Leaving open for four weeks for final comments. You can a similar effect using |
No further comments. |
Introduction
For ones who don't know what it is:
Swift
Kotlin
C#
Reasoning of string interpolation vs old school formatting
I used to think it was a gimmick but it is not in fact. It is actually a way to provide type safety for string formatting. I mean compiler can expand interpolated strings into expressions and perform all kind of type checking needed.
Examples
Syntax proposed
$
or{}
would be more convenient in my opinion, but we can't use them for compatibility reasons\(…)
notation would be compatible but these\()
are a bit too stealthyI guess
{…}
and\(…)
can be combined into\{…}
So, the interpolation of variable
variable
into some string may look likeFormatting also has formatting options. It may look like
Examples of options
etc
Conversions
There should be conversions and formatting support for built in types and for types implementing
error
andfmt.Stringer
. Support for types implementingcan be introduced later to deal with interpolation options
Pros and cons over traditional formatting
Pros
Cons
%v
(?),%T
, etc)The text was updated successfully, but these errors were encountered: