davecgh / go-spew

Implements a deep pretty printer for Go data structures to aid in debugging
ISC License
6.05k stars 364 forks source link

Feature Request: Support printing in Go syntax #6

Closed dmitshur closed 11 years ago

dmitshur commented 11 years ago

Right now spew does a great job of visualizing Go data structures for debugging purposes. I think it'd be useful to be able to print the data structures that is valid Go code to recreate the structure itself.

For instance, currently this code...

package main

import "github.com/davecgh/go-spew/spew"

func main() {
    spew.Config.Indent = "\t"

    type Inner struct {
        Field1 string
        Field2 int
    }
    type Lang struct {
        Name  string
        Year  int
        URL   string
        Inner *Inner
    }

    x := Lang{
        Name: "Go",
        Year: 2009,
        URL:  "http",
        Inner: &Inner{
            Field1: "Secret!",
        },
    }

    spew.Dump(x)
}

Will produce:

(main.Lang) {
    Name: (string) "Go",
    Year: (int) 2009,
    URL: (string) "http",
    Inner: (*main.Inner)(0x42166440)({
        Field1: (string) "Secret!",
        Field2: (int) 0
    })
}

But I'd like to be able to print x so that the output would be:

Lang{
    Name: "Go",
    Year: 2009,
    URL:  "http",
    Inner: &Inner{
        Field1: "Secret!",
    },
}

And/or:

Lang{Name: "Go", Year: 2009, URL: "http", Inner: &Inner{Field1: "Secret!"}}
davecgh commented 11 years ago

I considered this during initial development, but the main issue is that there are several cases where there is no clear Go syntax to recreate a given data structure. For example, consider the following slight modification of your example:

type Inner struct {
    Field1 string
    Field2 **int
}

In this case, there is no clear Go syntax such that you can initialize an instance of Inner due to the double pointer to an int.

The same issue arises in many scenarios such as double pointers to any type, single pointers to native types, interfaces, etc. I'm open to suggestions on how this should be handled, but I really don't see a clear way to support it.

dmitshur commented 11 years ago

I was afraid that there would be hard cases where it wouldn't be possible.

This is a hard question and I'm gonna think about it more and get back to here.

First thing that comes to mind is to support this for simple structures where it is possible, but I need to think it through more.

Somewhat related, what about simply supporting inline formatting? As in:

(main.Lang) {Name: (string) "Go", Year: (int) 2009, URL: (string) "http"}

Clearly it's best used for very simple structures, but could it be useful to have? What do you think?

davecgh commented 11 years ago

Inline formatting is already supported via the spew.F?Print{f,ln} family of functions. In particular, if you want full blown type information as above, you could do:

spew.Printf("%#v", yourStruct)

Does that not cover what you mean?

dmitshur commented 11 years ago

Ah, yeah, I forgot about that. Thanks - that is what I wanted. So the inline part is already there, this only leaves the question of making Go syntax formatting possible.

kdar commented 11 years ago

I would also like this. I think in the cases of pointers to built in types and double pointers, then just revert it to how spew normally does it in those cases. So we can have an output that is as close to Go as possible, where we would just have to manually clean it up after.

dmitshur commented 11 years ago

Today, I've cloned this repo and tried to modify it to produce the results I was looking for. I think I've found that this feature request doesn't belong in this library.

The intent of this library is "a deep pretty printer for Go data structures to aid in debugging." I think this change would conflict and deviate from that goal.

The main reason I wanted the above is because I'm looking for an encoding similar to "ecoding/gob", except a text-based human-readable/editable version of that. Something similar to JSON, but for Go. The goals there are a little different.

For instance, spew prints all fields, even ones with zero value. This makes sense for debugging, you want to know all fields and their values. For a gob/JSON-like encoding, you might as well skip zero fields since that's what they'll be initialized to anyway.

image

As @davecgh mentioned, there are many cases where there is no "inline" Go syntax to represent the in-memory variables. In such cases, spew does a great job of visualizing the nested structs and gives you a lot of useful information (e.g. multiple pointer chains show off all pointer values, and dereference the final pointer, also the types of each variable). Much of this run-time-only information would not be appropriate for a gob/JSON-like encoding.

image

As such, I plan to create a standalone library, with the spew codebase as a great starting point, with explicit intent to be a gob/JSON-like encoding. But I think spew should remain as is; I don't think this change would be an appropriate (nor accepted) pull request. (If things turn out differently, the two can always converge in the future if seen fit.)

kdar commented 11 years ago

Not a bad idea. Let me know when it's done so I can put it up on the golang projects wiki.

dmitshur commented 11 years ago

That's encouraging to hear. :)

I don't know when it'll be done, but I've started the spec here.

https://github.com/shurcooL/goon

Tomorrow I will flesh it out, and start working on the implementation (if I don't learn that this has already been done before). I still have to figure out whether I should follow the fmt package vs. encoding/gob package approach.

dmitshur commented 11 years ago

@davecgh, with your permission I'd like to base my first implementation of goon on your go-spew implementation.

Can I just take the existing go-spew implementation and modify it for the needs of goon, rather than writing my implementation from scratch?

If so, what's the best way to do that?

Should I fork this project and modify my fork (even though there are no immediate plans to use the fork to submit a PR to this project)? Or, since goon's intention is different, should I clone instead of fork?

Finally, about the license. Typically I put MIT license on my stuff (only because it seems I have to; I'd prefer not to put any license at all and just let people do whatever benefits humanity best). You already have a similar but slightly different license. Can I just replace it with MIT?

It felt strange just replacing the existing LICENSE file, that's why I wanted to ask for your feedback before continuing.

davecgh commented 11 years ago

@shurcooL

Sure, I don't mind you taking it and modifying as you see fit. That's one of the nice benefits of providing code under a liberal license. Feel free to convert it to the MIT license if you prefer. ISC and MIT are quite similar.

As far as fork versus clone, I think a clone is probably better in your case since your project goal is different and it's unlikely most of your changes would apply back to spew itself. Of course, that doesn't mean I wouldn't welcome pull requests if they apply!

Good luck with your new project. Sounds like it will be another useful Go tool for the community.

dmitshur commented 11 years ago

Thank you @davecgh, that sounds great.

The main reason I was considering a fork is because that seems to give you more credit for the base implementation. But, I wasn't sure if it's applicable for situations where the fork changes intention. I suppose it's a matter of human convention, hence it's hard to figure out without consulting with other people.

If it's not a fork, then I just have to decide if I should preserve spew's git repo history or start a fresh git repo... Not yet sure which is the better thing to do. Do you have any suggestions regarding that decision?

P.S. Please feel free to accept my humble tip here. Your work has made my life at least that much better, and I want you to have the ability to keep that up. :)

davecgh commented 11 years ago

@shurcooL

I agree that either approach works. My personal opinion is that a fork would indicate you plan to retain the original functionality and add some new functionality to it (printing Go syntax). My understanding is that you plan to modify it specifically to generate Go syntax while eliminating the run-time aspects. I definitely see the benefit of that functionality as well. Given that the code is surely going to morph significantly to meet the goals of your project, a new repo is probably most appropriate, but in the end, I really don't think it will matter which approach you choose. The most important part is there will be a new tool for the Go community that provides a useful feature.

I really appreciate your efforts to give attribution. It's a great quality to be a conscientious author. Also, thank you very much for the tip.

dmitshur commented 11 years ago

After working some more with a larger, less familiar data structures, I ended up backtracking on a few differences from spew:

  1. Skipping zero values. At first it seemed like a good idea, but I realized you might want to know/change fields with zero values, so it makes sense to include them.
  2. Not showing types of fields/field values. On a simple struct that I knew by heart this wasn't a problem, but with a larger one, I started to really miss the type info that spew did a great job of conveying.

To maintain the valid Go property, I included type information as comments. It seems to work and looks quite readable IMO.

https://github.com/shurcooL/go-goon/tree/6b752179ab4a7997f3e4f1492ecf0ba7af20dd8c#example-output

I just wanted to share that with you.

There are a few places were you can insert comments within a Go literal like that...

Field /*Comment 1*/ : /*Comment 2*/ value, //Comment 3

The circular references are simply set to nil for now, I'll have to think if there's a better way to deal with that (if I change goon spec to not require a Go literal, then potentially more code can be generated to setup the circular pointers).

davecgh commented 11 years ago

Looks like it's coming along nicely!

dmitshur commented 11 years ago

I've changed go-goon to resemble go-spew even more closely (while still being valid Go code in the easy cases) by using conversions of constants.

(Lang)(Lang{
    Name: (string)("Go"),
    Year: (int)(2009),
    URL:  (string)("http"),
    Inner: (*Inner)(&Inner{
        Field1: (string)("Secret!"),
        Field2: (int)(0),
    }),
})

IMO it's more readable than the comments previously, and it preserves the type information better. E.g., time.Now().Unix() returns an int64, which used to become a 12345000 // int64 which produces a constant of type int, not int64. Now, it would be (int64)(12345000) which has the same type.