-
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: language: add variadic maps #26459
Comments
Proper(map[string]interface{}{"age": 12, "location": "NYC"}) I'm fairly sure you can do: type blah map[string]interface{}
Proper(blah{"age": 12, "location": "NYC"}) But maybe I'm mistaken? |
Note that you can always implement this at run-time with variadic arguments, assigning the key-value pairs in the map. I'm personally not convinced that the added complexity to the language is worth avoiding a tiny amount of code in rare occasions. |
How common is that practice? I can't remember to ever have seen this. Also, how many of those usages are to emulate keyword-arguments and/or use strings as keys? I assume that this would likely (mostly) be abused for that purpose. Leading to a proposal to get real keyword-arguments, because the compiler can't check that keyword args you pass are defined. |
IMHO real keyword arguments are not needed, since we have structs. This is just for maps with any key types allowed that a regular map allows. One example for this practice is in here: https://docs.google.com/document/d/1shW9DZJXOeGbG9Mr9Us9MiaPqmlcVatD_D8lrOXRNMU/edit But I saw it lots of places. Maybe we could write a parser to find these places. |
It is used in the standard library for strings.NewReplacer. |
As I have written, the runtime implementation is not type safe and the call is prone to errors. If you check for these errors, the caller also has the burden of dealing with them (i.e. functions that otherwise would not need to return and error will need to return one). Also the proposal makes it easy for the caller and reader to see, where the |
In simple cases, like
I don't think this applies. Panicking when a function receives a wrong list of arguments is perfectly normal, as you can see from
This again reminds me of @Azareal's suggestion above. I think this issue boils down to you wanting to use maps, but not wanting to have to write the map type each time you use it in a call. I think #12854 is precisely what you want. Then, you could write either of:
|
Are you kidding?
No, definitely not. #12854 blurs the line between structs and maps from the caller and readers perspective. Also it is more verbose for the caller. |
I am not, and I'd recommend replying with arguments if you want your proposal to have a chance at being accepted.
I personally don't think there's any blurring, as all the expressions would still have a specific type. If anything, your proposal has exactly the same issue, as the caller doesn't explicitly state the map type. I also don't agree with the verbosity point. That proposal would simply add opening and closing braces to your calls, and I doubt that two characters are worth this much effort. |
Ok, you asked for it. First: refering to the current implementation of So do you want to say, that it is better to risk a runtime panic instead of preventing it via the compiler? A common case for such maps is logging or replacing, so places where a missing key or value would not be critical, at least not so much, that you want to bring the system into an undefined state. Because, if you needed to make sure a key is there, you would have used a struct to begin with. So maybe you don't want the hospital app to crash when printing or logging, for example. Panics should just be done, if you are in an undefined state anyway. Other places in the standard lib are signs that the type system currently lacks and must be handled with care. |
#12854 does blur for the reader, wether it is a struct or a map that is created in the background. My proposal and syntax is very clear that it is creating a map, since it is only about maps anyway. The map type is only needed, when the call is implemented, so there would be no difference to #12854. But when reading loads of code after years, you might not want to lookup every function signature just to know, if a map or a struct was used (they perform very differently). |
https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right Dave Cheney:
|
This isn't a black-and-white issue. It is neither always better to have runtime panics (otherwise Go would be Python), nor to never have runtime panics (otherwise Go would be Agda). Instead, Go takes a measured approach of tradeoffs, weighing the costs of more powerful type checking against the cost of bugs prevented. Sometimes, that tradeoff makes panicing a perfectly acceptable solution and it's used all over the stdlib and Go code in general. This is why it's important to actually talk about how large the risks involved are (i.e. how often is this pattern leading to bugs, crashes or toil?) and how commonly this pattern is used. There has to be quantitative discussion, not just a qualitative one. Another way to approach this, BTW, if the pattern is sufficiently rarely used (which I suspect) is to have a specific tool to check for this, which can be used by packages who use the pattern. It's not ideal, but it'd get you static checking while also not having to pay the cost of adding this to the language.
No one is forcing you to panic or return an error, you can simply drop the call, if it's that uncritical.
I wouldn't recommend Go for life-critical applications (and I highly doubt it would be approved for such uses) for exactly this reason. |
As others have noted, I really don't see the advantage over a config pattern. Configs have the (huge, IMHO) benefit of being type-safe. Example:
As far as I'm concerned, the issue at hand is really: does Go want to support real (i.e.: python-like) keyword arguments or not?
Certainly, half-baked solutions are significantly worse than the config pattern. |
One of the things I've not understood is the logic behind when you are allowed to elide types or not in Go. It seems like there are many places where it is unambiguous what the type is, but you are not allowed to elide it. It seems like one solution to having to type
would be to permit
Is there some reason this is not permitted, and/or some reason it is somehow completely undesirable? (Above and beyond just "that's not how it's currently done".) I will vouch for the fact that while this is not my largest problem with the language and it certainly doesn't keep me from using it for many things, this does have a real impact on my APIs, and I have also really had the temptation to use |
struct Arg {
key string
val interface{}
}
func F(args Arg...) {
m := make(map[string]interface{})
for _, arg := range args {
m[arg.key] = arg.val
}
}
var V = F(Arg{"Age", 12}, Arg{"Location", "NYC"}) Might be interesting especially in conjunction with #12854. |
Your example itself looks like a design issue. map[string]interface{} tells me there is to much JS thinking in your go. |
Just having ability to have named function arguments would be far superior than " There are many cases where having optional would make sense but very little where you want to have them and do not want to know type of them Like func f(Name string, ?Location int, ?Age int,) {}
....
f("foo",Age: 12,Location: "NYC")
f(Name: "bar", Age: 34)
f("foobar',"NYC",22) of course, then the code of function would have to check if value of variable is not the default one, but that is far less annoying than having to match types of every possible argument. Basically a config pattern, but with few annoying steps in-between steps removed |
but when you're dealing with
Except that then the type assertion will panic because it's nil so you'd probably have to do a whole cascade of control flow structures just to ensure you have all the right arguments with the right types.
The only scenario where I can see this being useful is if in fact you don't actually have to deal with the types of arguments as you're just passing them on to some other library or when you're working with arbitrary JSON... things like that. Maybe one case is whether you want to check whether an option was set or not which you can't do with
because then you can't distinguish between a set The only way I see this making a lot of sense is if you're dealing with lots of dynamic stuff where you have a dynamic amount of arguments you don't even know the names of at compile time but then you get pretty much no compile time guarantees anyway? In every other case I personally think the only way to do it
I agree with that. If there are a static number of arguments with static types then I'm strongly in favor of using structs rather than Personally, I'd even prefer the caller to pass in a map and use an utility function to create that map such as:
because then there's less code duplication and the callee doesn't need to deal with all of the errors. FWIW you could also write a very generic |
Let's not get too hung up on the use of
Current Go lets us elide the types in the map itself; the question is, can we do anything about the fully-known before that? And/or do we want to? |
Ok, to repeat the obvious: This is not about structs nor named parameters. The use case is a map. There is a reason for Go to have maps. You make it look like there is no need for maps and we could use structs everywhere instead. Also there is a reason for maps to be typed and of no fix length. That fits perfectly with this proposal. You are reading something into this proposal that is not there. Why is it a better fit to make map out of a variadic slice instead of having a variadic map? The reason, I am against type elision is that the reader can't see, if Blah({hi: "ho"}) The reason, I am against type config map[string]int
Blubb(config{"one": 1}) Is the need to introduce (and often need to export) types for each of such cases as well as comfort. type o []string
Blubb(o{"a","b"}) So no need for variadics at all - so why does Go have them? (If we'd argue that way.) |
Sorry, I didn't realize you were that focused on maps. Honestly to me that map case doesn't seem all that interesting. Proper(map[string]interface{}{"age": 12, "location": "nyc"}) You are adding a bit of syntactic sugar to something that I can already write, and it's even easier to write if I name the type |
@ianlancetaylor |
Variadic slices seem to me to be a much more common case in practice than variadic maps. The standard library has many examples of variadic slices, starting with |
@ianlancetaylor
But it is an unfair comparison, since variadic slices already exist and vadiadic maps not. The hack shows only, where they were needed so badly, that the developer chose to use the hack. (The introduction of an avoidable panic should not be taken lightely). However, if they would exist in the language, they would for sure be used more often than that. |
I claim responsibility for the use of the "broken" The |
It's just a grammar sugar that doesn't solve the underlying problem |
With respect, I beg to differ. Your use-case is a solution to a problem. It's this problem we should be discussing. I don't understand what there is to be gained by restricting the conversation to variadic maps. I agree with the pain-point that your proposal attempts to address, but for reasons I explained above, I don't think maps are the best solution. |
I don't think this is a good solution. The syntax is also unintuitive. (Sure, it comes from the Maps I believe are fine on their own and don't really need an additional syntax just for being passed into an argument. Instead, I'd rather see functions like The reason variadic slices exist in the first place is because they are used extremely often, so it's good to have a syntax which doesn't require us to type This isn't used often, and as stated earlier, the syntax is confusing. Especially with IDE helpers, people will wonder what the heck a I think #12854 is a good way for this to be done though. If it were accepted, we'd just accept a |
This proposal is syntactic sugar for something we can already do. It's not a sufficiently common case to merit additional syntax. |
I'm late to the party here, but I actually think the "functional options" pattern popular in several Go packages (https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html) solves an awful lot of the issues for this. It's extremely type safe (essentially to the same level as an options struct), allows for variadic argument passing, feels less clunky than trying to handle a zero-or-one length array for variadic options, and doesn't require users to generate structs just to pass in a few options. You can always make a quick function to marshal the options into a map or a custom struct or whatever you want, and it also lets you do things that are somewhat more sophisticated than a simple options struct. Consider that pattern instead; I think we should evangelize it more. |
Proposal: Add variadic maps to Go2
Problem
To get a nice API, it is common practice to misuse
variadic arguments
like this:when the proper argument would have been:
The reason for this common hack is the comfort when using
compared to
For the discussion the types of
keyvals
and the keys and values ofm
don't really matter - theyjust reflect typical examples.
What matters however is that the type signature of
Broken
does not reflect the real type that is needed and that results in code needed to manually check (or panic) what the compiler does ensure naturally inProper
.Solution
Since there seems to be a real need to have a more pleasant way to pass map keys and values to a function, it is proposed to introduce
variadic maps
to Go2.This is what the example would look like, when rewritten with a
variadic map
:Benefits:
Proper
Proper
variadic slice
vsvariadic map
For the sake of clarity, the variadic argument of Go1 is called
variadic slice
here, since they result in a slice while thevariadic map
results in a map.
Similarities:
variadic
can only be last argument of a function (this excludes the usage of avariadic slice
and a
variadic map
within the same function)non-variadic
arguments before thevariadic
.Differences:
variadic slice
variadic map
...
:::
variadic slice
are seperated with a,
variadic map
are key-value pairs, seperated with a,
, where each pair is the key, followed by a:
and followed by the value.variadic slice
by postfixing it with...
variadic map
by postfixing it with:::
complete example:
The text was updated successfully, but these errors were encountered: