-
Notifications
You must be signed in to change notification settings - Fork 1k
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: Dictionary Literals #414
Comments
Are you aware of the C# 6.0 syntax? http://www.kunal-chowdhury.com/2015/01/csharp-6-dictionary-initializer.html#IsRqzKMwhPJejbC1.97 ? Otherwise consider using the Python or JSON syntax. JSON literals would be welcome. |
Would these be compile time constants? |
Even array doesn't get it's "literal" type and it is more closely tied to the language. If we get this, we should get it for arrays as well. However I would go the opposite way, aligning array initializer expressions and dictionary initializer expressions. var arr = new int[] { 1, 2 }; //optionally with length
var arr = new[] { 1, 2 }; //interestingly, can not specify length
int[] arr = { 1, 2 }; //must specify target type
//interestingly 'int[] arr; arr = { 1, 2 };' does not work Currently allowed for dictionaries: var dic = new Dictionary<int, string> { { 1, "one" }, { 2, "two" } }; //optionally with capacity
var dic = new Dictionary<int, string> { [1] = "one", [2] = "two" }; //optionally with capacity If we want it improved, it should be done in the same spirit, which brings me to this: var dic = new { { 1, "one" }, { 2, "two" } }; //infers Dictionary<int, string>
var dic = new { [1] = "one", [2] = "two" }; //infers Dictionary<int, string>
//possibly 'new<,>' instead?
var dic = new<object,string> { { 1, "one" }, { 2, "two" } }; //uses Dictionary<object, string>
var dic = new<object,string> { [1] = "one", [2] = "two" }; //uses Dictionary<object, string>
//may not be needed as this would do the same:
// var dic = new { { (object)1, "one" }, { 2, "two" } }; //infers Dictionary<object, string> it however bakes in |
@migueldeicaza I think the problem with this is that it raises Even making In language-ext I'm able to get some very concise construction of its var x = Map(("foo", 4), ("bar", 5));
// x is a Map<string, int>
So it seems to me this would unnecessarily punish other implementations, and discourage safer coding with immutable types (they shouldn't be the default either, but this would make other types into second class citizens IMHO). |
Is specifying the type of the dictionary explicitly really such a big burden? It doesn't matter how long the set of things going into the dictionary is, you only need to specify the type once. How often are people initializing dictionaries to a set of hard coded values? Seems like a lot of new syntax for comparatively little benefit. |
I just had to write some code in F# for the first time in a while and I had forgotten how wonderful not specifying types can be. But when i am in C# land, my mentality is different. It is somewhat rare for me to specify collection literals, i'm typically initializing empty and then filling them from some data source. As a result i don't think this syntax (albeit pretty) pays for itself especially since we already have 2 different way to initialize dictionaries. |
Pretty much agree with all the down-vote comments on here. The inconsistency with Array initialisation, the promotion of Dictionary<,> over other similar types (Immutable, Concurrent), the further dilution of type clarity, etc.. We already had the C# 6.0 addition (which I really didn't think was needed either and felt it added nothing). Bringing more in line with Arrays would at least be a logical step, even though I still think we have too many styles already. I'm all for change and you can count me as a fan of many of the latest editions, but I think the bar needs to be a little higher than this proposal meets. |
I am in favour of custom literals like vb.net instead of this one - it would not favour any dictionary type over any other. Or there could be a IHasDictionaryLiteral interface that would allow to interpret the dictionary at design time for any type, but that would be confusing/complex and probably not worth it. |
Here's some food for thought. In my var x = Map(("foo", 4), ("bar", 5)); I wondered if it'd be possible to just use a single-item tuple of tuples and the use an implicit operator to convert to the type required. Map<string, int> x = (("foo", 4), ("bar", 5)); And it is possible. This doesn't solve the issue of having to specify the type, but for method arguments, fields, or properties where the type is already known you wouldn't have to specify it at the site. It does mean having to create operator overloads for however many items you'd be willing to support (see below). And it's certainly not the most efficient method of construction; but I wonder if there's some avenue here that could be explored? It's more of a thought experiment than anything; it certainly has a more attractive syntax. public static implicit operator Map<K, V>(ValueTuple<(K, V)> items) =>
new Map<K, V>(new [] { items.Item1 });
public static implicit operator Map<K, V>(((K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11, items.Item12 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11, items.Item12, items.Item13 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11, items.Item12, items.Item13, items.Item14 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11, items.Item12, items.Item13, items.Item14, items.Item15 });
public static implicit operator Map<K, V>(((K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V), (K, V)) items) =>
new Map<K, V>(new[] { items.Item1, items.Item2, items.Item3, items.Item4, items.Item5, items.Item6, items.Item7, items.Item8, items.Item9, items.Item10, items.Item11, items.Item12, items.Item13, items.Item14, items.Item15, items.Item16 }); |
Updated branch allows implicit conversions to Dictionary-shaped objects, just like collection initializers, so this works:
It requires the type to be known, otherwise if the type is inferred, we default to the original Patch: |
I used to think that having lots of different literals was important. I'm not so sure anymore. My "main" language is Ruby, which has a rich set of literals:
However, after using Scala for a while, I realized that Scala has no collection literals, and in fact only a rather small number of literals, and I didn't miss them. The reason for this is Scala's otherwise concise syntax, which makes special literals mostly unnecessary, because factory methods are syntactically convenient to use anyway:
Scala's string interpolation syntax allows "fake" user-defined literals, for example, the Scala stdlib provides an interpolator for regexs: Really, the only "complex" literals Scala has, are Tuples and Lambdas. (It also has XML Literals, but those have been deprecated for a long time and are considered a mistake.) Personally, I would prefer the following over selectively adding individual new literals for specific types:
In general, I am a fan of powerful general language features that make it possible to implement stuff in libraries as opposed to changing the language spec. Sure, you can add dictionary literals to the language. And then someone wants regex literals. Or complex literals. Or set literals. Or rational literals. Or money literals. I would much rather have a feature that allows users to implement all of those as libraries than having to change the language every time someone comes up with an idea for a new literal type. |
I second Joerg on the use of Scala-like features |
@louthy could you just use a params array of tuples Map<K,V>(params (K key, V value)[] keyValuePairs) |
@mattwar Hi Matt, I already do that for construction. My examples were related to the implicit conversion operator. Which as far as I'm aware can't take a sequence of tuples. |
I support this proposal and I think lists and dictionaries/maps are the two most fundamental types of collections used everywhere and should be syntactically lifted to language level. I believe this will make the beginners' life lot more easier. I also want to see this proposal for Lists as promised by the author. Another thing I would suggest is lifting the types also using same notation- |
That's not my understanding. No existing BCL type was lifted to become a syntactic construct; rather, ValueTuple was created only after the syntactic construct was desired for separate reasons. |
Tuples did had |
"Tuples" in the C# language weren't an attempt to "promote" the existing This is quite different from lists/dictionaries where there can be many different implementations and the language has gone to some length to avoid endorsing even the BCL flavors of those implementations. |
I want to have more aggressive type inference for C#, especially when it comes to generics but as much as it sounds attractive I wouldn't like to have a special syntax for a dictionary just because... I can't see the value in it but if it's going to be expanded to other types then it can be more interesting and appealing to me but even then I think that the proposed syntax needs some work. |
Instead of my previous proposal of I think this idea of array/list and dictionary literals is heavily influenced by Swift. We can take a look in their documentation (search with 'literal'). |
Without a bloat of default interface methods, those interfaces are severely underpowered to be used as the all-purpose dictionary and list. |
I also love the idea of |
I think that perhaps the most C#-ish way forward is to do something similar to what arrays do now with a special initialisation syntax: public class A
{
private readonly string[] values = new int[]
{
"one",
"two",
"three"
}
} Can currently be written as: public class A
{
private readonly string[] values =
{
"one",
"two",
"three"
}
} Can we allow the following: public class B
{
private readonly Dictionary<string, int> values = new Dictionary<string, int>
{
{ "one", 1 },
{ "two", 2 },
{ "three", 3 },
}
} To instead be written as: public class B
{
private readonly Dictionary<string, int> values =
{
{ "one", 1 },
{ "two", 2 },
{ "three", 3 },
}
} |
Well, once target-typed private readonly Dictionary<string, int> values = new()
{
{ "one", 1 },
{ "two", 2 },
{ "three", 3 },
} |
Just came into mind- |
@gulshan Can you give an example of where it would conflict? The locations where you can specify a list expression or an attribute seem mutually exclusive. |
Actually it would be best to term it as "confusing". Now, |
@gulshan |
I like it. It's like a third the length of what we currently have to do. |
I don't like the syntax. Currently square brackets are used for index and size, curly brackets are used for elements. This problem should be solved through new general language features like global aliases and better inference: using global Map = Systems.Collections.Generic.Dictionary;
...
Map x = new {{"foo",4}, {"bar",5}};//Length comparable to:
//var x = ["foo":4, "bar": 5]; If anything: var x = {{"foo",4}, {"bar",5}}; |
I'm guessing this particular ship has sailed because we already have so many ways to initialize objects and collections. Still, can't help but feel slightly envious of the low-effort inline object definitions that JavaScript/TypeScript has. In the world of C# it could look something like this: var obj = new { // or maybe even omit "new", although the cost is low if we only use it to open
field = "value",
anotherField = 16f,
objectReference = objectGetter.GetObject(),
tuple = (3, 5, 7, "surprise"),
array = [1, 2, 3, 4, 5],
objectArray = [
{ x = 3, y = 8}, // allow omitting of "new" inside an anonymous object declaration
{ x = 7, y = 5},
{ x = 0, y = 1},
],
dictionary = [
["key1": "value1"], // dictionary syntax proposed above
["key2": "value2"],
],
altDictionarySyntax = { // more like the old dictionary initialization
{ "key1", "value1" }, // syntax, but can we distinguish it from the
{ "key2", "value2" }, // anonymous object syntax if we omit "new"?
}, // or maybe use ":" as a key-value separator instead?
};
var json = JsonConvert.SerializeObject(obj); // feels good, man Some of the above is actually valid C# already. This is much more in line with the way you can effortlessly define tuples with the new C#7 syntax. In my opinion, fits together nicely overall. |
Aside from a few suggested alternatives that we have today, the problem with the proposal is that it only optimize for Dictionary and possibly List types when the target type is not known. As soon as you depend on another collection type, you'd need a target type. I wonder if more generalized features like |
LDM looked at this today. We think there are a number of interesting use cases around initializing data, particularly for things like immutable dictionaries. We don't find the existing syntax for initializing a dictionary that onerous, nor do we see it as a frequent pattern in code that would benefit much from a language feature. We thing that the general area of initializing data should be looked at again after we do records and withers. But this proposal doesn't feel compelling. |
@333fred you removed this proposal from REJECTED. I think it means that the proposal was deleted from this list. Is there any chance in the short term to see it again? |
No. I removed every proposal from the planning board because we didn't use it and didn't update it, so it showed often-incorrect data for current proposals. Unfortunately, removing everything then showed up in all the issues, leading to just as much confusion. The milestone of the issue is still Likely Never, LDM has not discussed this since. |
Note: this proposal is being rolled into #5354. So while dictionary-literals on their own is "likely never", the space of 'collection literals' (which includes dictionaries) is championed and is proceeding with its design. I'm going to lock this issue so that further discussion around it happens with the championed proposal. |
Proposal Moved
Note: this proposal is being rolled into #5354. So while dictionary-literals on their own is "likely never", the space of 'collection literals' (which includes dictionaries) is championed and is proceeding with its design.
We're going to lock this issue so that further discussion around it happens with the championed proposal.
Dictionary Literals
Summary
Dictionaries are a very common data structure, this is one step to make dictionaries better supported at the language level than they currently are.
Introduce a simpler syntax to create initialized
Dictionary<TKey,TValue>
objects without having to specify either theDictionary
type name or the type parameters. The type parameters for the dictionary are inferred using the existing rules used for array type inference.This makes working with dictionaries simpler in C# code. I will provide a separate proposal for a shorthand for the Dictionary type and Lists types.
Similar syntax exists in other languages in the space like F# and Swift.
Details
This is bound to the Dictionary<TKey,TValue> from mscorlib.dll. Future work can explore whether it is viable to use this as a instance initializer for other types.
The grammar is extended as follows:
The argument types for the Dictionary are computed similarly to how they are computed for array initializers, the type inference is applied over all the keys to determine the key type. Then type inference is applied over all the values to determine the value type.
A working proof of concept implemented on top of Mono’s C# compiler can be seen here:
https://github.com/mono/mono/compare/master...migueldeicaza:dictionary-literals?expand=1
The text was updated successfully, but these errors were encountered: