[swift-evolution] [swift-dev] Pure Cocoa NSNumbers and AnyHashable
Matthew Johnson
matthew at anandabits.com
Thu Nov 10 18:05:22 CST 2016
> On Nov 10, 2016, at 2:41 PM, Joe Groff <jgroff at apple.com> wrote:
>
>>
>> On Nov 10, 2016, at 11:51 AM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>
>>>
>>> On Nov 10, 2016, at 1:44 PM, Joe Groff <jgroff at apple.com <mailto:jgroff at apple.com>> wrote:
>>>
>>>>
>>>> On Nov 10, 2016, at 11:42 AM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>>>
>>>>
>>>>> On Nov 10, 2016, at 1:34 PM, Joe Groff via swift-dev <swift-dev at swift.org <mailto:swift-dev at swift.org>> wrote:
>>>>>
>>>>>
>>>>>> On Nov 10, 2016, at 10:30 AM, Philippe Hausler <phausler at apple.com <mailto:phausler at apple.com>> wrote:
>>>>>>
>>>>>> So I think there are a few rough edges here not just the hashing or equality. I think the issue comes down to the subclass of NSNumber that is being used - it is defeating not only hashing but also performance and allocation optimizations in Foundation.
>>>>>>
>>>>>> So what would we have to do to get rid of the “type preserving” NSNumber subclass?
>>>>>
>>>>> The type-preserving subclasses remember the exact Swift type that a value was bridged from, to preserve type specificity of casts so that e.g. `0 as Any as AnyObject as Any as? Float` doesn't succeed even if the Any <-> AnyObject round-trip involves ObjC, thereby providing somewhat more consistent behavior between Darwin and Corelibs-based Swift. If we were willing to give that up, and say that NSNumbers just flat-out lose type info and can cast back to any Swift number type, then it seems to me we could use the pure Cocoa subclasses.
>>>>
>>>> Would these only be value-preserving casts and return nil if information loss would occur? I think that might be preferable anyway. Maybe I’m just not thinking hard enough, but when would the original type information be important as long as information isn’t lost? When I interact with these casts and NSNumber (JSON parsing, etc) I generally *do not* want an attempted cast that would be value-preserving to ever fail.
>>>
>>> I'm inclined to agree that the cast should be value-preserving rather than type-preserving. There was concern about the behavior being different on Darwin and Linux, which is why we try to be type-preserving so that pure Swift code that uses number values with Any or other polymorphic interfaces behaves consistently with Cocoa Foundation code that has to traffic in NSNumber for the same effect.
>>
>> Are you saying that Swift on Darwin can’t have value-preserving behavior? It seems like I’m missing something here. If value-preserving is the desirable behavior can you elaborate on the specific challenges getting in the way of having that behavior everywhere?
>
> It would require a change to the type relationships between the number value types on Swift. They are currently plain, unrelated struct types, so you can't do something like:
>
> let x: Int = 1
> x as? Float // produces nil
>
> We could conceivably special-case the number types in Swift so that they do value-preserving casts, and maybe that's even a good idea, but we don't today.
What bothers me about the current behavior is that when you have a numeric value of type `Any` its casting behavior depends on how it was constructed. This makes it easy to write code that works with some numeric values of type Any and be badly broken for others. One can argue this is ok because the values have different types underlying types, but I think it turns out to be pretty confusing and problematic for numeric types in practice.
This problem is compounded by the fact that *most* of the time when we work with opaque numeric values we’re working with actually working with values that do cast to all of the standard library numeric types, but aren’t necessarily value-preserving. They can truncate or overflow.
To clarify what bothers me about the current behavior I’ll give an example:
let json = "{ \"one\": 1, \"onePointZero\": 1.0, \"onePointOne\": 1.1, \"onePointNine\": 1.1, \"largerThanInt8Max\": 270 }"
let data = json.data(using: .utf8)!
// casts always succeed, but might truncate or overflow
//let dict = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
// casts always succeed, but might truncate or overflow
//let dict: [String: Any] = ["one": 1 as NSNumber, "onePointZero": 1.0 as NSNumber, "onePointOne": 1.1 as NSNumber, "onePointNine": 1.9 as NSNumber, "largerThanInt8Max": 270 as NSNumber]
// casts always fail unless the target type matches the original type
let dict: [String: Any] = ["one": 1, "onePointZero": 1.0, "onePointOne": 1.1, "onePointNine": 1.9, "largerThanInt8Max": 270]
let oneAsDouble = dict["one"] as? Double // 1 or nil
let onePointZeroAsInt = dict["onePointZero"] as? Int // 1 or nil
let onePointOneAsInt = dict["onePointOne"] as? Int // 1 (truncated) or nil
let onePointNineAsInt = dict["onePointNine"] as? Int // 1 (truncated) or nil
let int8Overflow = dict["largerThanInt8Max"] as? Int8 // 14 (overflow) or nil
The net result of this is that it’s pretty hard to write correct code for numeric values of type Any.
The truncation and overflow are particularly troublesome because this behavior is exhibited when dealing with JSON values from external sources whose behavior could change on us leading to garbage values rather than more immediate nil values.
I would strongly prefer to see a single behavior that does not ever produce garbage values. It seems like value-preserving behavior is the only way to do that. That said, I have an open mind if there are other options. But I think we should try to do something better than we currently do.
The current behavior is very subtle, taking a nontrivial amount of effort to understand despite the fact that it’s not at all obvious that there is anything that one should pay attention to at all (I would guess that most programmers will just expect value-preserving casts until they run into a bug). IMO this is not a good thing for a use case that applies to just about every app out there.
Note: I’m only concerned with code that deals with numeric values of type Any here. If that requires a change in behavior to direct numeric type casts like your example I wouldn’t object, but I am not specifically asking for that.
Matthew
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161110/2f4ce70b/attachment.html>
More information about the swift-evolution
mailing list