What are your thoughts on this? Perhaps you've considered (and rejected) it already while designing Map?
It could go hand in hand with a V getOrElse(K key, V defaultValue) Map method. Which would be handy for other reasons as well, counting occurrences being the obvious example.
It would also get rid of the null value/not-found ambiguity and it plays well with Bob's Null-safety proposal. My prime interest is robustness, however.
Looking at a few other languages, Java returns null while .NET/C#'s generic Dictionary throws. Python's dict throws too.
/Olov
One reasons that exceptions got a bad name was that they have been
overused, or mainly used, to express unexceptional conditions.
A throwing Map.operator[] would differ from the null-returning
Map.operator[] by imposing a precondition (key must exist in map) and
would throw only if that precondition is broken, in the same manner
that List.operator[] throws when the index is out of bounds or
Queue.first throws when it's empty. That doesn't mean that the user
should test for key availability using try-catch and operator[].
dict:fetch(Key, Dict) = Val | EXIT
corresponds to V (or thrown exception) Map.operator[](K key)
dict:search(Key, Dict) = {found, Val} | not_found
corresponds to a special case of V getOrElse(K key, V defaultValue)
dict:is_key(Key, Dict) = Boolean
corresponds to Map.containsKey(K key) = bool
/Olov
yes, sorry. by "more subtle approach" I meant "more subtle approach
than usual", not specifically your proposal,
which did include those distinct operations. it was prompted mainly by
your closing remark about Java, C#, Python, etc.
think of it as supporting your proposal and suggesting that there
should perhaps be fewer unexceptional exceptions in other library
interfaces.
Map[key] returns null if key is not in the map. It could instead throw an exception, consistent with collections such as List and Queue. The upside would be possibly more robust code (assuming that not all map[key] call-sites check for null), the downside.. worse compiled-to-JavaScript performance perhaps? Are there other downsides?
It would also get rid of the null value/not-found ambiguity and it plays well with Bob's Null-safety proposal. My prime interest is robustness, however.
Looking at a few other languages, Java returns null while .NET/C#'s generic Dictionary throws. Python's dict throws too.
Valid point, though getOrElse would be preferred over that anti-pattern:
var value = map.getOrElse('key', null);
...which on the flip-side can take any default-value (not just null)
but on the flop-side is a bit more verbose than a null-returning
operator[]. But perhaps many would still use contains because it's
more familiar.
> I'm not totally sold on map[] returning null when a key isn't present (for
> one thing, it makes it trickier to tell if you have a present key whose
> value is null), but my nullability proposal was designed with this case in
> mind. With it, the return type of operator[] in Map would be T?, not T, so
> it's at least clear to callers that it may not return a T.
True. And it would need to be T? even in the case of a throwing
operator[] since the Map can contain null. I stand corrected. :)
> I believe Dictionary throws in C# mainly because you can instantiate
> generics with value types that aren't nullable. Java doesn't support
> primitive type arguments which is why it can get away with returning null.
That seems to be the reason since the pre-generic Hashtable indeed returns null.
I have a better understanding of the trade-off already - thanks!
/Olov
if I want to throw on a non-exist (or if more than one exists, in this
case)
myIEnumerableQuery.Single(); //throws if does not return exactly 1
result, otherwise returns result
If I don't want to throw:
myIEnumerableQuery.SingleOrDefault(); //returns the default(Type) for
that type, usually null, otherwise returns result
Just a thought.
On Dec 2, 2:03 pm, Olov Lassus <olov.las...@gmail.com> wrote:
Valid point, though getOrElse would be preferred over that anti-pattern:
var value = map.getOrElse('key', null);
...which on the flip-side can take any default-value (not just null)
but on the flop-side is a bit more verbose than a null-returning
operator[].
I personally prefer the simpler getOrElse that takes a value (not a
function). The more complicated getOrElse becomes, the bigger risk for
the containsKey-then-[] anti-pattern. Complex default-values can still
be handled the same as today with the current null-returning behavior.
var value = map.getOrElse('key', null); // instead of: var value = map['key'];
if (value == null) {
value = new List(999999);
}
But V getOrElse(K key, V default()) has it's merits too. It's similar
to putIfAbsent, if nothing else.
/Olov
> You really don't want to create the default every time just do discard it.
> You could do:
>
> var value = map.getOrElse('key', () => new List(999999));
>
> but that starts to feel a little too clever for my taste. The challenge here
> is that you really want both getting-a-value and
> flow-control-based-on-key-presence in one compact operation. Without
> pattern-matching, it's hard to do that well.
Could you elaborate on this a bit more? This is quite a good solution.
What are your reasons for rejecting it? Is "too clever" code for "too
much like Smalltalk to be acceptable to mainstream programmers?"
Colin
Colin Putney
3 December, 2011 2:04 PM
Could you elaborate on this a bit more? This is quite a good solution.
What are your reasons for rejecting it? Is "too clever" code for "too
much like Smalltalk to be acceptable to mainstream programmers?"
Colin
Bob Nystrom
2 December, 2011 4:35 PM
Valid point, though getOrElse would be preferred over that anti-pattern:
var value = map.getOrElse('key', null);
...which on the flip-side can take any default-value (not just null)
but on the flop-side is a bit more verbose than a null-returning
operator[].
Right. The above works for some cases, but doesn't work if you only want to create the default value when needed. Imagine:var value = map.getOrElse('key', new List(999999));
You really don't want to create the default every time just do discard it. You could do:var value = map.getOrElse('key', () => new List(999999));but that starts to feel a little too clever for my taste. The challenge here is that you really want both getting-a-value and flow-control-based-on-key-presence in one compact operation. Without pattern-matching, it's hard to do that well.
- bobOlov Lassus
2 December, 2011 3:03 PM
On Fri, Dec 2, 2011 at 7:57 PM, Bob Nystrom <rnys...@google.com> wrote:Try/catch blocks are pretty syntactically heavy in Dart, so if we did that, I suspect that people would just do: if (map.contains('key')) { var value = map['key']; } and then you're wasting time doing the lookup twice. C# handles this by having TryGetValue() which is a bit cumbersome but works. We can be a bit more flexible in Dart since it's dynamically-typed: any variable can hold a value of any type, unlike in C# where you could have a Dictionary of non-reference value type that doesn't allow null.
Valid point, though getOrElse would be preferred over that anti-pattern: var value = map.getOrElse('key', null); ...which on the flip-side can take any default-value (not just null) but on the flop-side is a bit more verbose than a null-returning operator[]. But perhaps many would still use contains because it's more familiar.
I'm not totally sold on map[] returning null when a key isn't present (for one thing, it makes it trickier to tell if you have a present key whose value is null), but my nullability proposal was designed with this case in mind. With it, the return type of operator[] in Map would be T?, not T, so it's at least clear to callers that it may not return a T.True. And it would need to be T? even in the case of a throwing operator[] since the Map can contain null. I stand corrected. :)I believe Dictionary throws in C# mainly because you can instantiate generics with value types that aren't nullable. Java doesn't support primitive type arguments which is why it can get away with returning null.That seems to be the reason since the pre-generic Hashtable indeed returns null. I have a better understanding of the trade-off already - thanks! /Olov
Bob Nystrom
2 December, 2011 1:57 PM
On Fri, Dec 2, 2011 at 7:14 AM, Olov Lassus <olov....@gmail.com> wrote:Map[key] returns null if key is not in the map. It could instead throw an exception, consistent with collections such as List and Queue. The upside would be possibly more robust code (assuming that not all map[key] call-sites check for null), the downside.. worse compiled-to-JavaScript performance perhaps? Are there other downsides?
Try/catch blocks are pretty syntactically heavy in Dart, so if we did that, I suspect that people would just do:if (map.contains('key')) {var value = map['key'];}and then you're wasting time doing the lookup twice. C# handles this by having TryGetValue() which is a bit cumbersome but works. We can be a bit more flexible in Dart since it's dynamically-typed: any variable can hold a value of any type, unlike in C# where you could have a Dictionary of non-reference value type that doesn't allow null.
It would also get rid of the null value/not-found ambiguity and it plays well with Bob's Null-safety proposal. My prime interest is robustness, however.
I'm not totally sold on map[] returning null when a key isn't present (for one thing, it makes it trickier to tell if you have a present key whose value is null), but my nullability proposal was designed with this case in mind. With it, the return type of operator[] in Map would be T?, not T, so it's at least clear to callers that it may not return a T.
Looking at a few other languages, Java returns null while .NET/C#'s generic Dictionary throws. Python's dict throws too.
I believe Dictionary throws in C# mainly because you can instantiate generics with value types that aren't nullable. Java doesn't support primitive type arguments which is why it can get away with returning null.
- bobOlov Lassus
2 December, 2011 10:14 AM
Map[key] returns null if key is not in the map. It could instead throw an exception, consistent with collections such as List and Queue. The upside would be possibly more robust code (assuming that not all map[key] call-sites check for null), the downside.. worse compiled-to-JavaScript performance perhaps? Are there other downsides?
What are your thoughts on this? Perhaps you've considered (and rejected) it already while designing Map?
It could go hand in hand with a V getOrElse(K key, V defaultValue) Map method. Which would be handy for other reasons as well, counting occurrences being the obvious example.
It would also get rid of the null value/not-found ambiguity and it plays well with Bob's Null-safety proposal. My prime interest is robustness, however.
Looking at a few other languages, Java returns null while .NET/C#'s generic Dictionary throws. Python's dict throws too.
/Olov
Hmm. What would be a good name... It should be something that uses the
vocabulary of maps, something that suggests location. And the keyword
should suggest the lack of something in that location, its absence.
How about something like:
map.at('key', ifAbsent: () => new List(999999))
There, that reads pretty well. :-)
Ok, humour aside, I *was* trying to avoid the all-too-common "Hey,
Dart should be more like my favourite language" post. That's just not
constructive. Dart is not Smalltalk any more than it is Javascript or
Java or C#. We obviously want to develop idioms that serve the Dart
community well, regardless of what languages they've used in the past
or what they're doing with Dart today.
Nevertheless, Dart does have a lot of features that are similar to
those of Smalltalk. It seems reasonable to adopt some of the library
patterns that have grown up around those features and stood up to a
few decades of use. Using closures rather than exceptions to handle
common or expected errors is a good candidate, I think. It solves all
the issues brought up in this thread, flexibly and succinctly.
Dismissing it as "too clever" without any further discussion doesn't
seem constructive to me.
Colin
Excellent! I find that library quality is at least as important as
language semantics in making a pleasant programming environment, so
I'm very glad they'll be getting some love.
> One thing to bear in mind is that Dart does not support non-local returns
> :-(. This was not a decision taken lightly. It's just hard to implement it
> efficiently on top of Javascript. That does mean that some tried and true
> Smalltalk idioms won't work as well in Dart.
Yeah, that's probably the right decision at this stage. :-(
Fortunately, non-local returns are the kind of thing that could be
added later, if and when circumstances change.
Cheers,
Colin