Pitch: really_is and really_as operators


(Charles Srstka) #1

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles


(Beta) #2

I have 3 qualms with this proposal as it stands:

- type(of:) will never lie to you.

The only question it won’t answer to your satisfaction is the dynamic type of the NSString you boxed up as an Any.

- No more keywords without significant justification.

I don’t buy the performance use case at all - if you were properly concerned about performance you would try to use as many of Swift’s static features as possible.

- Especially no more keywords that look like they belong in Rust or PHP!

There is no precedent for the spelling of these operations other than the suffixed punctuation. Given that they’re domain-specific, will definitely be hard to use (given that NSString(string: "Bar”) may not “really” given you an NSString yet that’s what you asked us to check for “really"), and will be obviated by the implementation of SE-0083, I can’t see a reason why we need any of this in the language proper.

~Robert Widmann

···

On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #3

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

···

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution < swift-evolution@swift.org> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later
in Swift 3 back in May and not having been heard from since then, with the
Swift 3 release looming closer and closer. However, the predictability
gains that would have been provided by this change remain desirable for
cases where one needs to know the actual dynamic type of an entity before
any bridging magic is involved. Additionally, performance-critical code may
desire the ability to check something’s type quickly without incurring the
overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and
really_as!. These operators would only return a positive result if the type
actually was what was being asked for, instead of something that might be
able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when
you need to know what you’ve actually got.

Charles

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


(Joe Groff) #4

It would be helpful to know why you want this. What are you trying to do?

-Joe

···

On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.


(Jaden Geller) #5

Taking that suggestion a step further, it's pretty easy to define a function that performs this sort of casting without bridging.

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) == U.self else { return nil }
    return unsafeBitCast(x, to: U.self)
}

···

On Aug 24, 2016, at 5:42 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jaden Geller) #6

Actually, the code I proposed does not work. First of all, it doesn't work for subtype relations. This can easily be fixed though by changing `== U.self` to `is U`. More importantly though, it fails when `T` is an existential since `type(of:)` looks within the existential to determine the type. It would be useful if there were a way to determine the *actual* runtime representation of the value.

···

On Aug 24, 2016, at 6:00 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Taking that suggestion a step further, it's pretty easy to define a function that performs this sort of casting without bridging.

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) == U.self else { return nil }
    return unsafeBitCast(x, to: U.self)
}

On Aug 24, 2016, at 5:42 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jaden Geller) #7

Scratch that last message. Determining what's stored in `Any` was Charles's original goal, and what I said made no sense. This should work:

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) is U.Type else { return nil }
    return x as! U
}

if let x = unbridgedCast(x, to: String.self) { ... }

···

On Aug 24, 2016, at 6:06 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Actually, the code I proposed does not work. First of all, it doesn't work for subtype relations. This can easily be fixed though by changing `== U.self` to `is U`. More importantly though, it fails when `T` is an existential since `type(of:)` looks within the existential to determine the type. It would be useful if there were a way to determine the *actual* runtime representation of the value.

On Aug 24, 2016, at 6:00 PM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

Taking that suggestion a step further, it's pretty easy to define a function that performs this sort of casting without bridging.

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) == U.self else { return nil }
    return unsafeBitCast(x, to: U.self)
}

On Aug 24, 2016, at 5:42 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jaden Geller) #8

Though this performs an *extra* check over just `x as! U`, it avoids bridging conversions. I don't think this extra check matters though because you wouldn't be using `Any` in code that's performance critical enough for this to matter, so I'm currently -1 on this proposal.

···

On Aug 24, 2016, at 6:09 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Scratch that last message. Determining what's stored in `Any` was Charles's original goal, and what I said made no sense. This should work:

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) is U.Type else { return nil }
    return x as! U
}

if let x = unbridgedCast(x, to: String.self) { ... }

On Aug 24, 2016, at 6:06 PM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

Actually, the code I proposed does not work. First of all, it doesn't work for subtype relations. This can easily be fixed though by changing `== U.self` to `is U`. More importantly though, it fails when `T` is an existential since `type(of:)` looks within the existential to determine the type. It would be useful if there were a way to determine the *actual* runtime representation of the value.

On Aug 24, 2016, at 6:00 PM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

Taking that suggestion a step further, it's pretty easy to define a function that performs this sort of casting without bridging.

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) == U.self else { return nil }
    return unsafeBitCast(x, to: U.self)
}

On Aug 24, 2016, at 5:42 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Callionica (Swift)) #9

I used this several months ago for extracting data from Any:

// Dynamic cast without bridging
func dynamicCast<T>(_ type: T.Type, _ v: Any)->T? {
    guard let result = v as? T where v.dynamicType is T.Type else {
        return nil;
    }
    return result
}

from http://www.callionica.com/developer/#any-equals

based on suggestion from Joe Groff

···

On Wed, Aug 24, 2016 at 6:11 PM, Jaden Geller via swift-evolution < swift-evolution@swift.org> wrote:

Though this performs an *extra* check over just `x as! U`, it avoids
bridging conversions. I don't think this extra check matters though because
you wouldn't be using `Any` in code that's performance critical enough for
this to matter, so I'm currently -1 on this proposal.

On Aug 24, 2016, at 6:09 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Scratch that last message. Determining what's stored in `Any` was
Charles's original goal, and what I said made no sense. This should work:

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) is U.Type else { return nil }
    return x as! U
}

if let x = unbridgedCast(x, to: String.self) { ... }

On Aug 24, 2016, at 6:06 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Actually, the code I proposed does not work. First of all, it doesn't work
for subtype relations. This can easily be fixed though by changing `==
U.self` to `is U`. More importantly though, it fails when `T` is an
existential since `type(of:)` looks within the existential to determine the
type. It would be useful if there were a way to determine the *actual*
runtime representation of the value.

On Aug 24, 2016, at 6:00 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Taking that suggestion a step further, it's pretty easy to define a
function that performs this sort of casting without bridging.

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) == U.self else { return nil }
    return unsafeBitCast(x, to: U.self)
}

On Aug 24, 2016, at 5:42 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

On Wed, Aug 24, 2016 at 19:09 Charles Srstka via swift-evolution < > swift-evolution@swift.org> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later
in Swift 3 back in May and not having been heard from since then, with the
Swift 3 release looming closer and closer. However, the predictability
gains that would have been provided by this change remain desirable for
cases where one needs to know the actual dynamic type of an entity before
any bridging magic is involved. Additionally, performance-critical code may
desire the ability to check something’s type quickly without incurring the
overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and
really_as!. These operators would only return a positive result if the type
actually was what was being asked for, instead of something that might be
able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when
you need to know what you’ve actually got.

Charles

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

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

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


(Matthew Johnson) #10

I have 3 qualms with this proposal as it stands:

- type(of:) will never lie to you.

The only question it won’t answer to your satisfaction is the dynamic type of the NSString you boxed up as an Any.

- No more keywords without significant justification.

I don’t buy the performance use case at all - if you were properly concerned about performance you would try to use as many of Swift’s static features as possible.

- Especially no more keywords that look like they belong in Rust or PHP!

There is no precedent for the spelling of these operations other than the suffixed punctuation. Given that they’re domain-specific, will definitely be hard to use (given that NSString(string: "Bar”) may not “really” given you an NSString yet that’s what you asked us to check for “really"), and will be obviated by the implementation of SE-0083, I can’t see a reason why we need any of this in the language proper.

One related topic to consider is exhaustive pattern matching for classes. Now that SE-0117 has been accepted it will be possible to do this for many classes (I would say most if it weren’t for Objective-C classes being so common in Swift and are imported as `open`). Supporting exhaustive pattern matching well would require some kind of syntax for matching the runtime type exactly. I have imagined this as being “exact match” cast operators, which is what the `really_*` operators are. Do you have an alternative in mind for exhaustive pattern matching if we do not introduce exact match cast operators?

···

On Aug 24, 2016, at 9:09 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

~Robert Widmann

On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Charles Srstka) #11

Because if you actually try that, it doesn’t work. type(of: bar) == NSString.self actually returns false, not true, because the actual instance belongs to a private subclass of NSString, not NSString itself. type(of: bar) == NSClassFromString(“__NSCFString”) works, but that doesn’t really help us.

Charles

···

On Aug 24, 2016, at 7:42 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?


(Xiaodi Wu) #12

Works on Linux :slight_smile:

···

On Wed, Aug 24, 2016 at 9:44 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Aug 24, 2016, at 7:42 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

Because if you actually try that, it doesn’t work. type(of: bar) ==
NSString.self actually returns false, not true, because the actual instance
belongs to a private subclass of NSString, not NSString itself. type(of:
bar) == NSClassFromString(“__NSCFString”) works, but that doesn’t really
help us.

Charles


(Xiaodi Wu) #13

Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why should
you expect `bar really_is NSString == true`? Isn't bar *not really*
NSString?

···

On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 9:44 PM, Charles Srstka <cocoadev@charlessoft.com> > wrote:

On Aug 24, 2016, at 7:42 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

Because if you actually try that, it doesn’t work. type(of: bar) ==
NSString.self actually returns false, not true, because the actual instance
belongs to a private subclass of NSString, not NSString itself. type(of:
bar) == NSClassFromString(“__NSCFString”) works, but that doesn’t really
help us.

Charles


(Xiaodi Wu) #14

Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why should
you expect `bar really_is NSString == true`? Isn't bar *not really*
NSString?

Ack, I take that back. __NSCFString is a subclass of NSString. Thus, this
works on macOS, as expected:

type(of: bar) is NSString.Type // true
type(of: bar) is String.Type // false
···

On Wed, Aug 24, 2016 at 9:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 9:44 PM, Charles Srstka <cocoadev@charlessoft.com> >> wrote:

On Aug 24, 2016, at 7:42 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

import Foundation

let foo: Any = "Hello"
type(of: foo) == String.self // true
type(of: foo) == NSString.self // false

let bar: Any = "Hello" as NSString
type(of: bar) == String.self // false
type(of: bar) == NSString.self // true

Why not this?

Because if you actually try that, it doesn’t work. type(of: bar) ==
NSString.self actually returns false, not true, because the actual instance
belongs to a private subclass of NSString, not NSString itself. type(of:
bar) == NSClassFromString(“__NSCFString”) works, but that doesn’t
really help us.

Charles


(Charles Srstka) #15

Hmm, this one actually does seem to work. I had expected that type(of:) would be bridged as well. Thanks; this is definitely better than the Mirror workaround, at least.

Charles

···

On Aug 24, 2016, at 8:09 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Scratch that last message. Determining what's stored in `Any` was Charles's original goal, and what I said made no sense. This should work:

func unbridgedCast<T, U>(_ x: T, to: U.Type) -> U? {
    guard type(of: x) is U.Type else { return nil }
    return x as! U
}

if let x = unbridgedCast(x, to: String.self) { … }


(Charles Srstka) #16

Because the pitch was for a version of ‘is’ which lacks the Objective-C bridging. The ‘is’ keyword matches subclasses of the type you specify as well as the type itself.

Charles

···

On Aug 24, 2016, at 9:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why should you expect `bar really_is NSString == true`? Isn't bar *not really* NSString?


(Xiaodi Wu) #17

Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why should
you expect `bar really_is NSString == true`? Isn't bar *not really*
NSString?

Because the pitch was for a version of ‘is’ which lacks the Objective-C
bridging. The ‘is’ keyword matches subclasses of the type you specify as
well as the type itself.

Sorry, I corrected myself :slight_smile: I neglected the part where you mentioned that
__NSCFString is a subclass of NSString (which I forgot was the case). But
as you can see, `type(of: bar) is NSString.Type` works just fine :slight_smile:

···

On Wed, Aug 24, 2016 at 9:58 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Aug 24, 2016, at 9:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Charles


(Charles Srstka) #18

Yeah, it does; thanks. I thought that ‘is’ on the type would be compromised in the same way as it normally is, so it’s good to know that there’s a way to make it honest. I still hate the bridging, and wish that SE-0083 were not apparently dead, but grumble grumble grumble, at least there’s a workaround. Is this documented anywhere?

Charles

···

On Aug 24, 2016, at 10:00 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Aug 24, 2016 at 9:58 PM, Charles Srstka <cocoadev@charlessoft.com <mailto:cocoadev@charlessoft.com>> wrote:

On Aug 24, 2016, at 9:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why should you expect `bar really_is NSString == true`? Isn't bar *not really* NSString?

Because the pitch was for a version of ‘is’ which lacks the Objective-C bridging. The ‘is’ keyword matches subclasses of the type you specify as well as the type itself.

Sorry, I corrected myself :slight_smile: I neglected the part where you mentioned that __NSCFString is a subclass of NSString (which I forgot was the case). But as you can see, `type(of: bar) is NSString.Type` works just fine :slight_smile:


(Xiaodi Wu) #19

Works on Linux :slight_smile:

And besides, on any platform where `type(of: bar) != NSString`, why
should you expect `bar really_is NSString == true`? Isn't bar *not really*
NSString?

Because the pitch was for a version of ‘is’ which lacks the Objective-C
bridging. The ‘is’ keyword matches subclasses of the type you specify as
well as the type itself.

Sorry, I corrected myself :slight_smile: I neglected the part where you mentioned that
__NSCFString is a subclass of NSString (which I forgot was the case). But
as you can see, `type(of: bar) is NSString.Type` works just fine :slight_smile:

Yeah, it does; thanks. I thought that ‘is’ on the type would be
compromised in the same way as it normally is, so it’s good to know that
there’s a way to make it honest. I still hate the bridging, and wish that
SE-0083 were not apparently dead, but grumble grumble grumble, at least
there’s a workaround. Is this documented anywhere?

This is where I'm out of my depth, but I'll take a stab at it:
I _think_ the metatype itself is bridgeable, but it can't lie about the
type-of-which-the-metatype-is-the-type.

···

On Wed, Aug 24, 2016 at 10:07 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Aug 24, 2016, at 10:00 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Wed, Aug 24, 2016 at 9:58 PM, Charles Srstka <cocoadev@charlessoft.com> > wrote:

On Aug 24, 2016, at 9:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Wed, Aug 24, 2016 at 9:47 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Charles


(Charles Srstka) #20

Custom bridging behavior. My specific use case is obsolete since SE-0112, but before that I had my own little hacky SE-0112 implementation, that looked kind of like this:

protocol CSErrorType: ErrorType {
    var userInfo: [NSObject : AnyObject] { get }
    func toNSError() -> NSError
}

toNSError() had a default implementation that generated an appropriate NSError from the userInfo. This was pretty good, although I had to be diligent about always using .toNSError() rather than “as NSError”, so that the bridging behavior would be called. However, do/try/catch returns errors as ErrorType, which meant I had to do a dynamic check each time I caught an error, which was tedious. So, I decided to put an extension on ErrorType. The most convenient thing to do would be just to define a default implementation for toNSError() on ErrorType, but since there’s no way to make a method introduced by a protocol get dispatched dynamically, that would mean that CSErrorType’s implementation would never get called, even when it was appropriate. So I added a dynamic cast:

extension ErrorType {
    func toNSError() -> NSError {
        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

However, NSError conformed to ErrorType, and the code in “as NSError” apparently didn’t dynamically check the type of the error, so now if you called .toNSError() on a _SwiftNativeNSError that was statically typed as ErrorType, it would end up double-wrapped inside *another* _SwiftNativeNSError, which then prevented such errors from unwrapping back to Swift errors properly. So, my first thought was to just check for that:

extension ErrorType {
    func toNSError() -> NSError {
        if let ns = self as? NSError {
            return ns
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

But of course that will fail because “as? NSError” will always be true. Maybe “is” would be more honest?

extension ErrorType {
    func toNSError() -> NSError {
        if self is NSError {
            return unsafeBitCast(self, NSError.self)
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

Nope, of course “is NSError” is always true as well.

What I ended up doing was using a horrible little hack using Mirror that I’m really quite un-proud of to figure out if the thing was really an NSError or not. Knowing from this thread that “is” actually becomes honest when used on type(of:), I could have used that instead (yes, this example mixes Swift 2 and Swift 3, so I would have used “dynamicType” at the time; it’s just an example):

extension ErrorType {
    func toNSError() -> NSError {
        if type(of: self) is NSError.Type {
            return unsafeBitCast(self, NSError.self)
        }

        if let csError = self as? CSErrorType {
            return csError.toNSError()
        } else {
            return self as NSError
        }
    }
}

Unfortunately, the unsafeBitCast would probably have still been needed in order to avoid the bridging behavior when using “as!”.

While this particular use case isn’t really a “thing” anymore, someone else might have a different reason to bridge something in a custom way, and it’d be nice to have a way not to have to jump through all these hoops.

Charles

···

On Aug 29, 2016, at 11:14 AM, Joe Groff <jgroff@apple.com> wrote:

On Aug 24, 2016, at 5:08 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

MOTIVATION:

SE-0083 appears to be dead in the water, having been deferred until later in Swift 3 back in May and not having been heard from since then, with the Swift 3 release looming closer and closer. However, the predictability gains that would have been provided by this change remain desirable for cases where one needs to know the actual dynamic type of an entity before any bridging magic is involved. Additionally, performance-critical code may desire the ability to check something’s type quickly without incurring the overhead of Objective-C bridging code.

PROPOSED SOLUTION:

I propose the following operators: really_is, really_as, really_as?, and really_as!. These operators would only return a positive result if the type actually was what was being asked for, instead of something that might be able to bridge to that type.

DETAILED DESIGN:

let foo: Any = "Foo"
let bar: Any = NSString(string: "Bar")

let fooIsString = foo is String // true
let fooReallyIsString = foo really_is String // true

let fooIsNSString = foo is NSString // true
let fooReallyIsNSString = foo really_is NSString // false

let barIsString = bar is String // true
let barReallyIsString = bar really_is String // false

let barIsNSString = bar is NSString // true
let barReallyIsNSString = bar really_is NSString // true

ALTERNATIVES CONSIDERED:

Stick with using an unholy combination of Mirror and unsafeBitCast when you need to know what you’ve actually got.

It would be helpful to know why you want this. What are you trying to do?