Design in Dictionary with optional value is ambiguous

I found that the design of Dictionary type in Swift is ambiguous.

Let start the discussion via an example first.

Example:

var d: [String : Int?] = ["aa": 123, "cc": 456, "bb": nil] // values are of type optional Int

d.count // 3, it is correct ???, I am not sure it is correct or not, but according to design, "bb": nil is an item so that it is correct.

d["cc"] = nil // this item with key "cc" is removed out of d WHY this item is removed? d[ ] should just do update or add new item instead of remove

print(d) // ["aa": Optional(123),"bb": nil]

d["bb"] = nil // this item with key "bb" is removed WHY this item is removed???

print(d) // ["aa": Optional(123)]

d["ee"] = nil // try to add an item with value nil into d BUT not add the new item into this dictionary. WHY???

print(d) // ["aa": Optional(123)]


I think the design of Dictionary type in Swift has some space to improve.

Because that Swift adds optional value into Dictionary so that Swift's designers need to think more careful about nil

When we accept "bb": nil as an valid item in Dictionary we should allow to add a new item with value of nil into dictionary.

When we change "cc": 456 to "cc": nil, it should just change the value to nil, instead of removing the item out of the dictionary.


By the way, in the current design of Dictionary, We use the

removeValue(forKey:)
methodto remove a key-value pair from a dictionary .

I think the method name "removeValue(forKey:) is not a proper name, it is better to change it to removeKey() because that key is the keypoint of an item in Dictionary and the type of key is a nonoptional type. (Swift does not allow [String? : Int] )


By the way I also check Array in Swift, it seems OK no problem to work with optional value.

it allows [Int?], for example, [1, nil, 3, nil, nil, 6] (its count is 6), see example below


var a:[Int?] = [1,2,nil,4,5]

print(a.count)

a[1] = nil

print(a)

print(a.count)


a.append(nil)

print(a)

print(a.count)


Output:

5

[Optional(1), nil, nil, Optional(4), Optional(5)]

5

[Optional(1), nil, nil, Optional(4), Optional(5), nil]

6



any ideas from you?

You're in the tricky realm of optional optionals. Given a dictionary d of type [String: Int?] and a String called 'key', d[key] is of type Int??. This means being a bit more explicit about your values.


d["cc"] = nil

eliminates the key-value pair ("cc": 456); "cc" is no longer in the dictionary.


Alternatively,

d["cc"] = .Some(nil)

changes the key-value pair to ("cc": nil).

Um, what's even the point of [String : Int?]? I can't think of a meaningful distinction between nil (key not in the dictionary) and nil (stored). They're both nil, right? If you just use [String : Int], you can set values to nil and it works fine.

The reason that


     d["cc"] = nil // by convention, inferred type is Int?? not Int?


removes the value for key "cc" is that this behavior was requested by developers, many of whom come from a Cocoa background where this is an established convention for removing keys from NSDictionary objects, and where nil values are not allowed. Talking about what it "should" do is pointless, because different developers have different opinions, and Swift had to pick one convention. Optional dictonary values are not a common pattern amongst the current developer community. Not wrong, just not common.


It looks to me that you should be able to use updateValue(_:forKey:) to set a nil value into a dictionary where the value type is Int?:


     d.updateValue (nil, forKey: "cc")


Also, in a playground under Xcode 7.3.1, the following statement:


     d["cc"] = nil as Int?


correctly sets the value of key "cc" to nil, rather than deleting it, as does:


let cc: Int? = nil
d["cc"] = cc


IOW, as long as you inform the compiler of your intentions by specifying the correct type, it does what you expect. Given that what you're doing is unusual, I don't think this is a particularly onerous compromise to your ability to express yourself.

Um... maybe because the value he's modeling is an actual Optional(Int)?? I can think of several reasons for [String: Int?]. One obvious example would be backing a UI for entering Key->Value Ints; you don't want to drop user-entered keys before they provide a number value.


In any case, as stated in other replies, the assignable subscript is a convenience for a subset of Dictionary's functionality. You can express what you want just fine by using the more explicit statements as noted.

Yes, I knew the way by using d["cc"] = .Some(nil) to change the value to nil, but this is not what I wat to discuss here.

The problem that I am talking here is the design of Dictionary.

To a newer in Swift he needs to know how to remove an item from a dictionary and how to update the value (including nil) of a given key. It is better to separate between removal an item and update a value. in array, we can use like a[3] = nil to set its value become nil, but in Dictionary, it is totally different. if you use like d["cc"] = nil Swift will remove the item instead of update the value.

This issue makes Swift not easy to learn.

I tested 3 cases below:

d["cc"] = nil as Int? // it will update the value to nil

d["cc"] = nil as Int?? // it will remove the item

d["cc"] = nil // it will remove the item


But in Array, for example, ar: [Int?] = [11, 22, 33]

ar[2] = nil // it will update the value, not remove the item.


The behaviour of the assignment operator in both said cases (dictionary and array) is different it will cause new Swift users confused.


The problem will be resolved if d["cc"] = nil is the same as d["cc"] = nil as Int? instead of Int??

In this:


var ar: [Int?] = [11, 22, 33]
ar[2] = nil


the compiler is faced with the problem of inferring the type of the literal 'nil'. It can do this by looking at the return type of the array subscript method, which is 'Int?', so the above is the same as:


var ar: [Int?] = [11, 22, 33]
ar[2] = nil as Int? // no other choices are possible


In the case of a dictionary:


d["cc"] = nil


the return type of the subscript method is 'Int??', which means there are two possible type inferences:


d["cc"] = nil as Int?? // choice #1
d["cc"] = nil as Int? // choice #2


The non-specifc case:


d["cc"] = nil


is not a third choice, but ambiguous syntax that the compiler must resolve into #1 or #2. The current compiler treats it as #1, because that's the most useful.


>>The behaviour of the assignment operator in both said cases (dictionary and array) is different it will cause new Swift users confused.


You're comparing different things. The Array<Element> subscript method is declared like this:


subscript (_ index: Int) -> Element


The Dictionary<Key,Value> subscript method is declared like this:


subscript (_ key: Key) -> Value? // nil Value == "no such key"


Array subscripting returns non-optionals. Dictionary subscripting uses optionals to indicate keys not in the dictionary. What's potentially confusing is using optionals for the Dictionary "Value" type, because the 2 levels of optionality serve different purposes.


Array and Dictionary are different types, with different behaviors and semantics. The fact that the difference might not be immediately obvious to a new user, in all its details, is not necessarily a fault.

The behaviour of the assignment operator in both said cases (dictionary and array) is different it will cause new Swift users confused.

Honestly, I think folks who are new to Swift should steer clear of dictionaries with optional values. Such a setup is always going to be confusing; there’s no way to make everyone happy here IMO.

Did you encounter this problem yourself? Or is this just a concern about new users in general?

If it’s the former, I’m interested to hear what you were trying to model when you fell down this rabbit hole.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I agree that "Array and Dictionary are different types, with different behaviors and semantics."

But in design point of view, we should design (implement) their behaviors more similar instead of big difference. For example, We all know it is not a good design if Traffic light in USA has a big difference to in Canada.

It goes without saying, in this topic what I would like to discuss is the design of behaviors between Array and Dictionary. I think the current design make Swift not friendly to newers.


By the way about the removeValue(forKey:) method to remove an item, from design point of view I think it is not a good method's name. It is better to change it to removeKey() because , key is the keypoint in (key, value) pair and also key is nonoptional.

>> we should design (implement) their behaviors more similar instead of big difference


I think it's necessarily a big difference. As I said before, Dictionary subscripting uses optionals to indicate keys not in the dictionary. That is to say, when this expression returns nil:


d["dd"]


then the nil is actually 'nil as Int??'. This is a nil value, but the value is not in the dictionary, it's a marker that the value is absent. That is, for Dictionary, nil represents absence, not optionality. Indeed, when the Value type of Dictionary<Key,Value> is a non-optional type — the usual case — there is no real optionality in the Dictionary API, any more than there is in the Array API. That's the reason why this:


d["cc"] = nil


is best understood as removal, because it's meaningful for all Value types, whereas your proposed meaning is only relevant in the (unusual) case where the Value type is some kind of optional.


Incidentally, I think there's another way of looking at this. You can conceptualize a Dictionary as containing every possible value of its Key type, with a nil value for most of the possible keys. From this point of view, there is no difference between absence of a value, as opposed to presence of a 'nil as Value?'. This has always been a viable conceptualization of NSDictionary (I mean, code using dictionaries would be the same whether such nil values were stored or not), and that "bias" has naturally carried across to Swift Dictionary.


Finally, if there's possibility of confusion in 'removeValue(forKey:)', there's just as much possibility of confusion in 'removeKey()'. In the first case, someone might wonder, "If you remove the value, what happens to the key?". In the second case, someone might wonder, "If you remove the key, what happens to the value?" Either way, it's necessary to learn that the key and the value come and go together.

Design in Dictionary with optional value is ambiguous
 
 
Q