Go: fmt: maps should format in sorted order

Created on 20 Jul 2017  路  33Comments  路  Source: golang/go

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

FrozenDueToAge Proposal Proposal-Accepted

Most helpful comment

I think that will just lead to people wanting more control over the formatting. I strongly prefer just leaving things alone.

All 33 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?

@martisch

If stable output is needed one could dump and sort the map outside of fmt and then output the result.

Actually, this is not possible because golang string representation values are unfortunately not unique. For example look at this map value:

map[answer:42 hello:13]

Is this a map with keys answer & hello which are both ints?
OR
Is it a map with one key named answer which has string: 42 hello:13 ?

They display the same.

@martisch

Making fmt always sort the map will always incur time and allocation overhead

You're making the assumption, you didn't test or profile this. Since it would only come out when folks did a fmt: %+v then I don't think it will affect anyone negatively.

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, and how do you look inside interfaces to sort them?
The obvious sort order for pointers may not be stable across executions of your program.
So, it's complicated. Simple cases like string or int keys may sound tempting, but doing this in general is hard. Maybe not impossible, but I'm not convinced it would be worth the trouble.

On Thu, Jul 20, 2017 at 1:18 AM, Keith Randall notifications@github.com 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.

I've had a long day so maybe I'm missing something

https://play.golang.org/p/RYac90kI-H

On Thu, Jul 20, 2017 at 1:45 AM, Brad Fitzpatrick
notifications@github.com 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

Can you elaborate on your NaN comment with an example please?

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.

Actually, this is not possible because golang string representation values are unfortunately not unique.

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.

You're making the assumption, you didn't test or profile this. Since it would only come out when folks did a fmt:聽%+v聽then I don't think it will affect anyone negatively.

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 notifications@github.com 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
notifications@github.com 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 fmt does, even regardless of implementing Formatter), and we can already sort strings, so we _could_ do this if we wanted. It sounds like we don't want to.

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:

  • ints, floats, and strings sort by <

    • special case: NaN less than non-NaN floats

  • bool sorts false before true
  • complex sorts on real, then imag
  • pointers sort by machine address
  • channel values sort by machine address
  • struct sorts on each field in turn
  • array sorts on each element in turn
  • interface values sort first by reflect.Type describing the concrete type and then within type by value as described in the previous rules

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: fmt: print maps in key-sorted order

Change https://golang.org/cl/143178 mentions this issue: text/template: drop unused sortKeys function

Would it be possible to get this sorting behavior exposed somewhere, maybe as reflect.Less(v1, v2 interface{}) bool for parity with reflect.DeepEqual()? Could also use it to build a reflect.Lesser() to work with reflect.Swapper().

@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:

Maps are now printed in key-sorted order to ease testing. ...

https://tip.golang.org/doc/go1.12#fmt

This will be especially useful for writing Example style tests:

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 ;)

Was this page helpful?
0 / 5 - 0 ratings