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: Go 2: named varargs #27163

Closed
neilpa opened this issue Aug 22, 2018 · 8 comments
Closed

proposal: Go 2: named varargs #27163

neilpa opened this issue Aug 22, 2018 · 8 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@neilpa
Copy link

neilpa commented Aug 22, 2018

Not sure if this has been brought up before. Couldn't find anything when searching through old proposals for vararg.

Abstract

Roughly, I'd like to have named values for a variable argument list that maps to map[string]T in the callee.

log.Err("something went wrong", foo="bar", asdf=1)

func Err(msg string, args ...map[string]interface{}) {
  // ...
}

Obviously a go2 thing given the nature of the change but I'm curious if there's interest or glaring flaws I'm missing. Happy to flush out more of the details and dig into the compiler. I'm curious what kind havoc this might have on the grammar, parser, runtime, etc.

Background

This idea was born out of a discussion for an ergonomic API for dynamic, structured logging. As I see it there are roughly three approaches, in my personal preference order.

Map/struct passing

Best compromise of safety and readability (IMO)

type L map[string]interface{}
log.Err(err, "something went wrong", L { "foo": "bar", "asdf": 1 })

Alternating varargs

Most concise but loses a lot of safety.

log.Err(err, "something went wrong", "foo", "bar", "asdf", 1)

Fluent

Safest, but find this pattern awful from a readability perspective

log.Err(err).Str("foo", "bar").Int("asdf", 1).Msg("something went wrong")

All of these left me wanting something better in terms of a cleaner API at the call-site. Hence the named varags idea.

Questions

  • Can ellipsis be used to keep the same syntax but attach to maps instead of slices?
  • Can map[string]T be supported rather than just map[string]interface{}?
  • Does = make sense for binding keys to values? Or would : be better to align with map literals? Or something else entirely?

Design

TODO - Will fill this out if there's traction

@gopherbot gopherbot added this to the Proposal milestone Aug 22, 2018
@ianlancetaylor
Copy link
Contributor

May be related to #26459.

@ianlancetaylor ianlancetaylor changed the title proposal: named varargs proposal: Go 2: named varargs Aug 22, 2018
@ianlancetaylor ianlancetaylor added LanguageChange v2 A language change or incompatible library change labels Aug 22, 2018
@neilpa
Copy link
Author

neilpa commented Aug 24, 2018

Thanks for the link, very much related to #26459 in both the rationale and original idea for implementation. Apparently I needed to search for variadic and not vararg.

I read through the thread and it seems like the main feedback is that it's mostly syntax sugar. I tend to agree with that point and can see the arguments against it. Especially if #12854 gets accepted, there's almost no added value.

However, I still think there's potential value in what I called "named varargs" (or maybe it should be "named variadic args"). Being able to seamlessly associate a name to a value at the call-site in a dynamic manner while retaining type safety is what I'm looking for. Beyond logging, I think there are other serialization contexts where this would be useful and provide a cleaner interface.

I have a rough alternative idea, quite different from my initial proposal. I think it would resolve the "syntax sugar" point as well as maintaining argument order. Need a bit of time to flush it out and write it up though.

@ianlancetaylor does it make sense to repurpose this proposal with the new idea (especially since the original is so close to #26459) or should I open a new one when it's ready?

@ianlancetaylor
Copy link
Contributor

Probably clearer to open a new one, thanks. We aren't short on issue numbers.

@yesuu
Copy link

yesuu commented Sep 5, 2018

If name:1 or name=1 is a literal or expression of key-value pair, can it be use only in the function argument list?

In this case, go will have another design that is not uniform. If you want to look uniform, you need to assign name:1 or name=1 to variables and constants.

a := name:1
a := name=1
const a = name:1
const a = name=1

then what is the type of this key-value pair?

Look at the function signature.

func Err(msg string, args ...map[string]interface{})

a map only store a key-value pair?

func Err(msg string, args map[string]interface{}) 

This is not very good.

func Err(msg string, args ...[string]interface{})
func Err(msg string, args ...string:interface{})

@neilpa
Copy link
Author

neilpa commented Sep 5, 2018

If name:1 or name=1 is a literal or expression of key-value pair, can it be use only in the function argument list?

That was the original idea. My end goal is to "solve" verbosity at the call-site where you commonly duplicate names. Instead of

Err("something went wrong: foo=%v", foo)

func Err(msg string, args ...interface{})

I would like to do

Err("something went wrong: %v", foo)

func Err(msg string, args ...var)

And both would produce something went wrong: foo=42.

Essentially I want to "lift" the variable into something that is type-safe while also capturing the name from the call-site. I've been trying to work out a proposal for this but haven't had much time. I also keep feeling like I'm falling down some sort of hybrid generic/reflection territory.

My thought on var in the argument list is to leverage an already existing keyword. I was originally thinking of a "symbol" type drawing on ideas from Clojure/Ruby but that is purely the name piece.

Throwing this out there in case someone else wants to run with the idea (if it hasn't already been discussed). I likely won't have too much time in the coming weeks to flush it out. Maybe I'll be able to take another stab at it though.

@bcmills
Copy link
Contributor

bcmills commented Sep 6, 2018

Essentially I want to "lift" the variable into something that is type-safe while also capturing the name from the call-site.

That is traditionally the role of macros.

Have you considered using a macro preprocessor and //go:generate? That would at least allow you to do some experimenting, and having the results of that experiment would potentially make a stronger case for any future proposal along those lines.

@neilpa
Copy link
Author

neilpa commented Sep 6, 2018

Interesting, it's been a long time since I've done C/C++ so that didn't even cross my mind. Thanks for the idea!

I'm guessing there may be some interesting ideas in how Rust does type-safe macros that I should dig into as well.

@ianlancetaylor
Copy link
Contributor

Like #26459, this is syntactic sugar for a case that can already be handled and is not especially common.

@golang golang locked and limited conversation to collaborators Oct 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

5 participants