Currently Dart maps return null when a lookup fails. With null safety, that means operator[] always returns a nullable type. That can be annoying.
Consider if Map had three type parameters: K, V and D where D is the type of the default value returned when a lookup fails.
It would be Map<K, V extends D, D>, and operator[] has return type D.
To avoid breaking everyone we could introduce a new Map type, XMap (probably not), and have all current Map<K, V> instances implement XMap<K, V, V?>.
Then we can have XMap constructors taking a default value, rather than just always using null as the default value.
Map literals could write {"x": 1, "y": 2, default: -1} to create an XMap.
Obviously we'd want to rename XMap to Map soon enough, but without breaking code which only knows the old Map. That could be based on language version, and maybe type aliases.
Old code sees typedef Map<K, V> = real.Map<K, V, V?>;, new code sees the real Map directly.
(Maybe they'll want a some short type aliases too, like typedef map<K, V> = Map<K, V, V>; )
I'm looking forward to be able to do a Map<K, Option<V>> with default: const None().
I feel -1 like yet another null without any language support.
I feel
-1like yet anothernullwithout any language support.
He is only giving a example of the default syntax for a literal Map. He is not saying -1 is the default for integer maps or anything like this.
I'm also giving an example. My point is that null is better than other default values, because null has many supportive futures such as ?, !, non-null promotion with flow analysis and most importantly null pointer exception (no such method error).
I wonder whether this really covers the common case? It seems to me that there are two kinds of indexes into a map:
key in the map, and want its valuekey is in the mapIn the first case, users should prefer not to get a nullable return type, but should be mostly agnostic between throwing an exception or returning a default value (though they may prefer to get an exception since if they have a bug, the exception will surface).
In the second case, it's not clear to me what users will mostly want.
a[k] ?? b)So it's really not clear to me that getting a single default value is what users will want most of the time. I actually suspect that we would cover more use cases if we added a separate operator (maybe use the call operator) which indexed and threw if the value was not there. But really, this feels like something that would be nice to get data on.
I agree @leafpetersen.
In Ruby, a language I've spent a lot of time with, they support square brackets for "gimme the value or null" and fetch(key) for "gimme the value or throw." And then they have an optional block parameter for fetch that lets you provide a default value, like so: map.fetch('foo') { 'default' }
I wonder if that sort of API would feel good to dart users. I know that I would like it and it's not that different from the methods on Iterable that take orElse. In dart it could even be: map.fetch('key', orElse: () => 'default').
Using a lambda is a nice way to allow for new instances of the default value to be used on each fetch. For example, if you set the default value at Map construction time and you use a type like List, then each time you'd have a key miss, would you get a new List or a reference to the one default List instance created at Map construction time? If it's the latter then ant mutations to that List will likely result in surprises for other key misses that return a non empty List.
Anyway, I think a fetch method might be a good alternative approach.
cc @pq @bwilkerson I just noticed in passing that my last comment here was relevant to our discussion about gathering data. Could be a kind of interesting test case. How many uses of the map index operator:
a[k] ?? d)d a constantThere are other interesting things you could look for, but they might be harder to track, e.g.
- Are assigned to a variable that is then checked for null and used to choose different code paths based on null/not-null
Is this still under consideration?
I'd keep it open as an enhancement request, but it's not NNBD-specific.
Most helpful comment
I wonder whether this really covers the common case? It seems to me that there are two kinds of indexes into a map:
keyin the map, and want its valuekeyis in the mapIn the first case, users should prefer not to get a nullable return type, but should be mostly agnostic between throwing an exception or returning a default value (though they may prefer to get an exception since if they have a bug, the exception will surface).
In the second case, it's not clear to me what users will mostly want.
a[k] ?? b)So it's really not clear to me that getting a single default value is what users will want most of the time. I actually suspect that we would cover more use cases if we added a separate operator (maybe use the call operator) which indexed and threw if the value was not there. But really, this feels like something that would be nice to get data on.