> On Nov 10, 2016, at 2:41 PM, Joe Groff <jgr...@apple.com> wrote:
> 
>> 
>> On Nov 10, 2016, at 11:51 AM, Matthew Johnson <matt...@anandabits.com 
>> <mailto:matt...@anandabits.com>> wrote:
>> 
>>> 
>>> On Nov 10, 2016, at 1:44 PM, Joe Groff <jgr...@apple.com 
>>> <mailto:jgr...@apple.com>> wrote:
>>> 
>>>> 
>>>> On Nov 10, 2016, at 11:42 AM, Matthew Johnson <matt...@anandabits.com 
>>>> <mailto:matt...@anandabits.com>> wrote:
>>>> 
>>>> 
>>>>> On Nov 10, 2016, at 1:34 PM, Joe Groff via swift-dev <swift-...@swift.org 
>>>>> <mailto:swift-...@swift.org>> wrote:
>>>>> 
>>>>> 
>>>>>> On Nov 10, 2016, at 10:30 AM, Philippe Hausler <phaus...@apple.com 
>>>>>> <mailto:phaus...@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
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to