-
Notifications
You must be signed in to change notification settings - Fork 17.5k
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
fmt: maps should format in sorted order #21095
Comments
Observation: Even with sorting we will not always get stable output. e.g. NaNs will always be in some random order and sorting them will not help. If stable output is needed one could dump and sort the map outside of fmt and then output the result. Making fmt always sort the map will always incur time and allocation overhead that can not be avoided even if unstable output is fine. Maybe we can add a special modifier that will try to make output as stable as possible for printing maps instead of modifying the default unordered behaviour. |
@martisch Can you elaborate on your NaN comment with an example please? |
Actually, this is not possible because golang string representation values are unfortunately not unique. For example look at this map value:
Is this a map with keys They display the same. |
You're making the assumption, you didn't test or profile this. Since it would only come out when folks did a fmt: |
Sorting the keys presupposes there is a sort order on the keys. For complicated keys like
It's not clear how to sort them. Floats don't always sort correctly, and how do you look inside interfaces to sort them? |
On Thu, Jul 20, 2017 at 1:18 AM, Keith Randall ***@***.***> wrote:
Sorting the keys presupposes there is a sort order on the keys. For
complicated keys like
struct {
x int16
y float64
z interface{}
p *int
}
It's not clear how to sort them. Floats don't always sort correctly
I've had a long day so maybe I'm missing something, but we're talking
about map's, not structs.
We sort by the key name.
|
Map keys are guaranteed to be comparable, but they're not guaranteed to be ordered - sorting them is not even possible in the general case. |
|
On Thu, Jul 20, 2017 at 1:45 AM, Brad Fitzpatrick ***@***.***> wrote:
I've had a long day so maybe I'm missing something
https://play.golang.org/p/RYac90kI-H
Gotcha, thanks!
So I think we could plausibly have in the worst case something that
*usually* provides good results. If in certain corner cases a display
isn't stable, I don't think it's the end of the world. It would
certainly be pleasing for the general cases.
Thanks
|
NaNs can not be ordered and we only get them back random from ranging over a map. So sorting them afterwards we will not get a stable output across calls unless the map itself provides a mechanism to return key, values in stable order.
I did not mean to suggest to sort the string representation but to sort the a slice constructed by collecting all key,value pairs in a slice using the range statement over a map.
I did not know your proposal was to only have ordered output for %+v as the proposal or example did not mention it. Println uses the %v internally. Note that using + also affect how keys and values will be printed for the map and always adds field names. |
Added a correction in previous comment where i confused +v with regular + modifier. |
alternatively: have your map type implement fmt.Formatter |
As observed, if you want your map to print a certain way, you can make that happen with Stringer or Formatter. That is, if you care about the format, you should care enough to write the code to control the format, which will be specific to your problem and not generally applicable. In general what you ask is otherwise impossible. |
@robpike Isn't making this generally more useful for everyone in a single place (upstream) preferable to having everyone hit this? Making things "batteries included" in this scenario is positive for the language IMO. Thanks |
@purpleidea Except it can't be done! |
On Thu, Jul 20, 2017 at 11:03 PM, Rob Pike ***@***.***> wrote:
@purpleidea Except it can't be done!
Why not with a heuristic to satisfy *most* scenarios?
|
I can only recall one time I've ever needed a map to print in a specific order, and it was during a test and a for loop took care of that problem. Adding non-deterministic printing to fmt seems a bit overkill for a very specific problem that few users encounter, no? |
On Fri, Jul 21, 2017 at 12:07 AM, Eric Lagergren ***@***.***> wrote:
Adding non-deterministic printing to fmt
That's what it's doing now... We'd be consistent with that or better!
|
but it's consistently non-deterministic :-) |
It we really wanted to, we could define that the sorting is based on the stringified output of the map keys & values. That is, we can already stringify everything (that's what |
I'm milestoning this as Proposal in the hopes that the proposal review group will make a decision on it soon. It seems to me like there's a decent amount of consensus, anyway. Speaking of which, I didn't know that issues could have the proposal label, but not the proposal milestone. I've been trying to search what that means on the wiki, but haven't been successful. I assume it means something like "in between NeedsDecision and a full Proposal". |
I think this should not be done by fmt. First, a precise complete definition is either unspecifiable or too complex and expensive, like generating the individual strings and then sorting those. Second, as written above, people who really care about printing their maps will probably write their own String method anyway. Third, the right sort will depend on the user's own preferences, which argues for a variety of implementations, not one canonical one. And finally, who prints maps raw anyway? The format is suitable only for debugging. So write an external package that solves the problem for you and call fmt.Print(myformatter.Map(m)). |
We discussed this at proposal review and it seems like maybe we should consider a rule that fmt displays maps sorted by key when the key is a Go type for which the Go < operator is defined (basic types except bool and complex). We understand that this applies essentially only when debugging, but that's exactly when you might want a little extra help, and the extra work is limited. From a dependency point of view, sort is lower in the hierarchy than fmt. In practice there's already a []reflect.Value pulled from the map, so there would be four possible sorts: by v.String, v.Int, v.Uint, or v.Float64 (or not at all). /cc @robpike |
I think that will just lead to people wanting more control over the formatting. I strongly prefer just leaving things alone. |
FWIW, I'd still like to see this done (and sorting by the stringified form as a fallback when otherwise tricky). |
@robpike and I talked about this. We realized that text/template already does exactly what I proposed above (order by < operator when available, leave alone otherwise). But probably it is reasonable to finish the job and just define an order on all comparable types for purposes of printing, and then make fmt and text/template both use it, perhaps in a shared internal package. Defining a complete sort order for printing might be something like:
I don't know what "sort by reflect.Type" means. Maybe PkgPath, Name, String, and finally underlying pointer value. That's not perfect but it's probably good enough. @robpike will look into this for Go 1.12. |
Change https://golang.org/cl/142737 mentions this issue: |
Change https://golang.org/cl/143178 mentions this issue: |
Recent change golang.org/cl/142737 drops the only call site for the sortKeys function. If it's not in use, it should probably not be there in the code, lurking and preparing to bite us when someone calls that instead of the new key sorter in fmtsort, resulting in strange inconsistencies. Since the function isn't called, this should have no impact. Related to, but does not fix, #21095. Change-Id: I4695503ef4d5ce90d989ec952f01ea00cc15c79d Reviewed-on: https://go-review.googlesource.com/c/143178 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
The PreNest marker allows us to drop the empty-string delimiters from library callers and still get readable output from fmt.Print-style renderers. I've added a new unit test showing that log implementations that want to detect Markers should use reflect.DeepEqual or something else that is stricter than ==. I've also adjusted the expected order of keys in the unit-test map. Currently this output is unstable, but [1,2,3] will have the order stabilized in future Go releases. The change here sets us up for compatibility with that new logic. I've also made the Newf and Logf implementations more compact by turning them into wrappers around New and Log respectively. [1]: https://go-review.googlesource.com/c/go/+/142737/ [2]: golang/go@a440cc0 [3]: golang/go#21095
Would it be possible to get this sorting behavior exposed somewhere, maybe as |
@DeedleFake That should be a separate proposal, this one is closed. I tend to think that exposing the comparison function would be an attractive nuisance. In particular it doesn't sort pointer values reliably across executions of the same program, and it doesn't sort slices at all. |
@DeedleFake I agree with @ianlancetaylor. It was a deliberate decision to keep this implementation private; the phrase 'attractive nuisance' is good. The code is not fast (it's very hard to do well) and not general (there are cases such as Ian mentioned). Making it more generally available will result in pushback on those problems, which are really not worth worrying about. |
Looks like this will be added in Go 1.12:
https://tip.golang.org/doc/go1.12#fmt This will be especially useful for writing func ExampleCounts() {
d := []float64{1, 2, 3, 4, 4, 4, 4}
c := Counts(d)
fmt.Println(c)
// Output: map[1:2 2:1 3:1 4:4]
} |
\o/ I guess it was a good idea after all ;) |
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?go version go1.8.1 linux/amd64
What operating system and processor architecture are you using (
go env
)?GOARCH="amd64"
GOHOSTOS="linux"
GCCGO="gccgo"
CC="gcc"
What did you do?
I ran the attached playground link.
https://play.golang.org/p/EjvBekU87U
What did you expect to see?
I expect the displayed
map
output to be deterministic.What did you see instead?
It changes randomly.
I understand that map randomizes the key order, and I actually think this is a cool implementation feature, however I think the displayed text output should be deterministic. It could display by sorted key order for example.
This will make diff-ing logs, and viewing output more consistent, with less flip-flop.
Thanks,
James
The text was updated successfully, but these errors were encountered: