Dictionary with optional values

Hi, here is the playground snippet:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil
noOptDict // “one” is gone

var optDict: [String: Int?] = ["one": 1, "two": 2, "three": nil]
optDict["one"] = nil
optDict // “one” is gone but “three” is still there

So the first dict instance works as it should, the second using opt values allows to store nil but deletes the key when you assign nil. Is it bug, feature, or both?

Best wishes,
Artyom

I'm not in front of Xcode, so I can't confirm this, but I suspect that `optDict["one"] = nil as! Int?` will set "one" to nil, rather than removing "one".

Whatever the rules for inferring the type of `nil` when an Optional<T:NilLiteralConvertible> is involved are, it seems like it always infers the one I don't want.

Hope that helps.

- Dave Sweeris

···

On May 18, 2016, at 05:56, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

Hi, here is the playground snippet:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil
noOptDict // “one” is gone

var optDict: [String: Int?] = ["one": 1, "two": 2, "three": nil]
optDict["one"] = nil
optDict // “one” is gone but “three” is still there

So the first dict instance works as it should, the second using opt values allows to store nil but deletes the key when you assign nil. Is it bug, feature, or both?

Best wishes,
Artyom
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Wow, interesting. To me this was surprising behavior too.

The comment for Dictionary subscript says:

    /// Access the value associated with the given key.
    ///
    /// Reading a key that is not present in `self` yields `nil`.
    /// Writing `nil` as the value for a given key erases that key from
    /// `self`.

Which is exactly what it is doing. As the Zhaoxin said, you can use updateValue (and removeValueForKey) to get better results when dealing with optional dictionary values.

Ray

···

On May 18, 2016, at 3:56 AM, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil

I think you should use updateValue forKey method instead of subscript=value.
Subscript in Dictionary returns an Element?, you should not use it like a subscript in Array, which returns Element.
Zhaoxin

···

Get Outlook for iOS

    _____________________________
From: Artyom Goncharov via swift-users <swift-users@swift.org>
Sent: 星期三, 五月 18, 2016 6:56 下午
Subject: [swift-users] Dictionary with optional values
To: <swift-users@swift.org>

Hi, here is the playground snippet:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil
noOptDict // “one” is gone

var optDict: [String: Int?] = ["one": 1, "two": 2, "three": nil]
optDict["one"] = nil
optDict // “one” is gone but “three” is still there

So the first dict instance works as it should, the second using opt values allows to store nil but deletes the key when you assign nil. Is it bug, feature, or both?

Best wishes,
Artyom
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

There’s a bit more discussion of this on the Apple Swift blog: Optionals Case Study: valuesForKeys - Swift Blog - Apple Developer <Optionals Case Study: valuesForKeys - Swift Blog - Apple Developer;

Jordan

···

On May 18, 2016, at 09:38, Ray Fix via swift-users <swift-users@swift.org> wrote:

On May 18, 2016, at 3:56 AM, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil

Wow, interesting. To me this was surprising behavior too.

The comment for Dictionary subscript says:

   /// Access the value associated with the given key.
   ///
   /// Reading a key that is not present in `self` yields `nil`.
   /// Writing `nil` as the value for a given key erases that key from
   /// `self`.

Which is exactly what it is doing. As the Zhaoxin said, you can use updateValue (and removeValueForKey) to get better results when dealing with optional dictionary values.

Yes, of course I can use API method but this kind of behaviour for subscript operator seems inconsistent(or even magical) to me because it is possible to initialise a dictionary with nil without casting it. Though nil is a special case it is still a value in the set of all values of a T? type, am I wrong?

···

On 18 May 2016, at 18:38, Ray Fix via swift-users <swift-users@swift.org> wrote:

On May 18, 2016, at 3:56 AM, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil

Wow, interesting. To me this was surprising behavior too.

The comment for Dictionary subscript says:

  /// Access the value associated with the given key.
  ///
  /// Reading a key that is not present in `self` yields `nil`.
  /// Writing `nil` as the value for a given key erases that key from
  /// `self`.

Which is exactly what it is doing. As the Zhaoxin said, you can use updateValue (and removeValueForKey) to get better results when dealing with optional dictionary values.

Ray

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Hi, here is the playground snippet:

var noOptDict = ["one": 1, "two": 2, "three": 3 ]
noOptDict["one"] = nil
noOptDict // “one” is gone

var optDict: [String: Int?] = ["one": 1, "two": 2, "three": nil]
optDict["one"] = nil
optDict // “one” is gone but “three” is still there

So the first dict instance works as it should, the second using opt values allows to store nil but deletes the key when you assign nil. Is it bug, feature, or both?

It’s correct behaviour, albeit confusing.

The type of a dictionary subscript is Optional<V> where V is the value type. If V is itself Optional<T> the the type of the subscript is Optional<Optional<T>> (T?? for shorthand).

Normally, when you assign a value in a context where an optional is required, the compiler implicitly wraps the value as an optional. i.e.

    let foo: Int? = 2
    let bar: Int? = nil

is compiled as if you wrote

    let foo:Int? = Optional<Int>.Some(2)
    let bar: Int? = Optional<Int>.None

When you have a nested optional type combined with the implicit conversion, the meaning of nil becomes ambiguous since it could either be the .None value of the outer type or a .Some value of the outer type wrapping the .None of the inner type.

    let foo: Int?? = nil

could be

    let foo: Int?? = Optional<Optional<Int>>.Some(Optional<Int>.None)

or

    let foo: Int?? = Optional<Optional<Int>>.None

depending on where you stick the implicit wrapping. The Swift compiler chooses the latter.
    
You need to force the compiler to choose the former. The most terse way I have found to do this so far is

   optDict["one"] = Int?.None

···

On 18 May 2016, at 11:56, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

Best wishes,
Artyom
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

3 Likes

It’s an unfortunate ambiguity, one that comes up in any dictionary API that potentially allows nil to be stored as a value. Does a RHS of nil mean to remove the key/value pair, or to store a nil as the value?

In particular, the C++ STL goes through horrible contortions to get around this, which is part of what makes it so awful and verbose to use. :-P

The intuitive (and most commonly useful) interpretation of “dict = nil” is to remove the key, which is what Swift does. If you’ve created a dictionary of optionals and need to store a null, you have to add a bit of verbosity to make your intention clear. I think that’s fine: it goes along with Alan Kay’s maxim that “simple things should be simple, and complex things should be doable.”

(Thinking about it, I can’t see much use for a dictionary of optionals. What’s the difference between “x has no value” and “x has a value of nil”? I guess it’s that when you iterate the keys you see x. This seems like a tricky use that could easily confuse someone reading the code (who could be you, a year later!) Personally I’d prefer a different, clearer solution, unless this was something performance-critical that led to faster code. In which case I’d add lots of comments!)

—Jens

···

On May 18, 2016, at 11:03 AM, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

Yes, of course I can use API method but this kind of behaviour for subscript operator seems inconsistent(or even magical) to me because it is possible to initialise a dictionary with nil without casting it. Though nil is a special case it is still a value in the set of all values of a T? type, am I wrong?

There are a few ways to assign nil to a dictionary entry. First of all

dictionary[“key”] = nil

always removes “key" from dictionary. To assign a nil value use one of:

dictionary[“key”] = Optional(nil)
dictionary[“key”] = .Some(nil)
dictionary[“key”]? = nil

That last only works if “key” already exists in the dictionary, i.e. you are replacing the existing value. The first two will add a dictionary entry if necessary.

I suspect now that you’ve been bitten by this you will remember it forever :)

Marc

···

On May 18, 2016, at 11:03 AM, Artyom Goncharov via swift-users <swift-users@swift.org> wrote:

Yes, of course I can use API method but this kind of behaviour for subscript operator seems inconsistent(or even magical) to me because it is possible to initialise a dictionary with nil without casting it. Though nil is a special case it is still a value in the set of all values of a T? type, am I wrong?

This isn't limited to Optional. Any dictionary whose value type conforms to NilLiteralConvertible can be "vulnerable" to some pretty subtle bugs.

- Dave Sweeris

···

On May 18, 2016, at 13:16, Jens Alfke via swift-users <swift-users@swift.org> wrote:

(Thinking about it, I can’t see much use for a dictionary of optionals. What’s the difference between “x has no value” and “x has a value of nil”? I guess it’s that when you iterate the keys you see x. This seems like a tricky use that could easily confuse someone reading the code (who could be you, a year later!) Personally I’d prefer a different, clearer solution, unless this was something performance-critical that led to faster code. In which case I’d add lots of comments!)

—Jens
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

In objective-c I have come across something like this a lot where a NSDictionary has been created from JSON an a NSNull is used to represent an actual null in the source JSON versus the absence of the key, most of the time I have had to just convert the NSNull to a nil, but I did have a situation where I had to treat the two differently with the absence of key falling back to a default value but NSNull meaning explicitly null, in swift you could have an actual nil in the dictionary.

···

Sent from my iPhone

On 19 May 2016, at 6:16 AM, Jens Alfke via swift-users <swift-users@swift.org> wrote:

Thinking about it, I can’t see much use for a dictionary of optionals. What’s the difference between “x has no value” and “x has a value of nil”? I guess it’s that when you iterate the keys you see x. This seems like a tricky use that could easily confuse someone reading the code (who could be you, a year later!) Personally I’d prefer a different, clearer solution, unless this was something performance-critical that led to faster code. In which case I’d add lots of comments!

Yeah, this comes from JavaScript, which weirdly has both ‘null’ and ‘undefined’ values; they’re kind of similar but not the same, and the latter is more like what we think of as null/nil in Swift or Obj-C. I think this was a bad design, and unfortunately it crept into JSON, which was based on JavaScript literals.

—Jens

···

On May 18, 2016, at 7:35 PM, Nathan Day <nathan_day@mac.com> wrote:

In objective-c I have come across something like this a lot where a NSDictionary has been created from JSON an a NSNull is used to represent an actual null in the source JSON versus the absence of the key

I stumbled upon this thread after researching this subject when I realised that at a first glance [K: V] and [K: V?] behave the same way, since you can assign nil to a key and can get one when retrieving a value under a key not present in the collection. As long as you don't use the syntax shown by @Marco_S_Hyman (dictionary["key"]? = nil, which is non-obvious) the semantics between these two versions is identical.

The retrieval part is quite understandable, since not all types have some kind of default value and it would be silly to require values of dictionary to implement some kind of DefaultRepresentable protocol. It would be even more silly to get a value for a key not present in the keys collection.
The assignment was a bit more confusing for me before reading this thread, but now seems quite obvious.

What I really wanted to ask, though, is if it has ever been considered to resolve this potential confusion? I could see two possibilities:

  1. Make it so that for a dictionary of non-optional values one wouldn't be able to assign nil directly to remove entry, but rather was forced to use removeValue(forKey:) method.
  2. Force all dictionaries to have optional values explicitly (so [K: V] would be illegal). - This would be bad, just wanted to state it.

While I realise that this thread is 2 years old I wanted to get this question out of my mind.

Resolve what, though? The confusion arises from forgetting that an undecorated nil is subject to type inference. When explicitly annotated, there's no confusion:

dict[key] = nil as V? // inserts/replaces
dict[key] = nil as V?? // deletes

(where the dictionary is [K:V?])

As it happens, undecorated nil is V?? by the usual type inference in this context, which is why it deletes.

As it should: it's exactly as documented that dict[key] = nil deletes, for any instance of Dictionary; anything else would be broken.

This is a fantastic explanation of how this works! Thanks @jeremyp