Here’s another argument in favor of the pitch:

Take an array:

let array = ["Foo", "Bar”]

We can convert this to an NSArray via the bridge:

let nsArray = array as NSArray

We can also convert this to a CFArray:

let cfArray = array as CFArray

Now, let’s convert them back.

let nsUntypedArray = nsArray as Array
let cfUntypedArray = cfArray as Array

This works, but both arrays are now Array<AnyObject>, which probably isn’t what 
we want. Since Swift arrays care about type, and NS/CFArrays generally don’t, 
we’ll want to do a check when converting them back:

let nsToNativeArray = nsArray as? Array<String>
let cfToNativeArray = cfArray as? Array<String>

Checking the value of the first one there, we get a nice Optional(["Foo", 
"Bar”]), as expected. However, checking the second one reveals that it now 
contains *nil!* Worse, the bug won’t be discovered until runtime, and may be 
hard to track down, since the code above *looks* fine.

Adding an intermediate cast to NSArray, of course, makes it work fine:

let cfToNativeArray = cfArray as NSArray? as? Array<String> // Optional(["Foo", 
"Bar"])

This may be a bug, maybe even a known one. However, if this had been done via 
initializers on Array rather than via bridging magic, the compiler would have 
thrown a type error when we tried to pass a CFArray to Array’s initializer if 
Array didn’t have an initializer that took a CFArray. The bridge, however, just 
cheerfully returns nil at runtime, leaving you with no idea something’s wrong 
until it all blows up mysteriously at runtime.

So basically, I guess I’m +1 on the pitch.

Charles

> On Apr 29, 2016, at 5:00 PM, Joe Groff via swift-evolution 
> <[email protected]> wrote:
> 
> When we introduced Swift, we wanted to provide value types for common 
> containers, with the safety and state isolation benefits they provide, while 
> still working well with the reference-oriented world of Cocoa. To that end, 
> we invested a lot of work into bridging between Swift’s value semantics 
> containers and their analogous Cocoa container classes. This bridging 
> consisted of several pieces in the language, the compiler, and the runtime:
> 
> Importer bridging, importing Objective-C APIs that take and return NSString, 
> NSArray, NSDictionary and NSSet so that they take and return Swift’s 
> analogous value types instead.
> 
> Originally, the language allowed implicit conversions in both directions 
> between Swift value types and their analogous classes. We’ve been working on 
> phasing the implicit conversions out—we removed the object-to-value implicit 
> conversion in Swift 1.2, and propose to remove the other direction in 
> SE–0072—but the conversions can still be performed by an explicit coercion 
> string as NSString. These required-explicit as coercions don’t otherwise 
> exist in the language, since as generally is used to force coercions that can 
> also happen implicitly, and value-preserving conversions are more 
> idiomatically performed by constructors in the standard library.
> 
> The runtime supports dynamic bridging casts. If you have a value that’s 
> dynamically of a Swift value type, and try to as?, as!, or is-cast it to its 
> bridged Cocoa class type, the cast will succeed, and the runtime will apply 
> the bridging conversion:
> 
> // An Any that dynamically contains a value "foo": String
> let x: Any = "foo"
> // Cast succeeds and produces the bridged "foo": NSString
> let y = x as! NSString 
> Since Swift first came out, Cocoa has done a great job of “Swiftification”, 
> aided by new Objective-C features like nullability and lightweight generics 
> that have greatly improved the up-front quality of importer-bridged APIs. 
> This has let us deemphasize and gradually remove the special case implicit 
> conversions from the language. I think it’s time to consider extricating them 
> from the dynamic type system as well, making it so that as?, as!, and is 
> casts only concern themselves with typechecks, and transitioning to using 
> standard initializers and methods for performing bridging conversions. I’d 
> like to propose the following changes:
> 
> Dynamic casts as?, as! and is should no longer perform bridging conversions 
> between value types and Cocoa classes.
> Coercion syntax as should no longer be used to explicitly force certain 
> bridging conversions.
> To replace this functionality, we should add initializers to bridged value 
> types and classes that perform the value-preserving bridging operations.
> The Rules of as[?]
> 
> Our original goal implementing this behavior into the dynamic casting 
> machinery was to preserve some transitivity identities between implicit 
> conversions and casts that users could reason about, including:
> 
> x as! T as! U === x as! U, if x as! T succeeds. Casting to a type U should 
> succeed and give the same result for any derived cast result.
> x as! T as U === x as! U. If T is coercible to U, then you should get the 
> same result by casting to Tand coercing to U as by casting to U directly.
> x as T as! U === x as! U. Likewise, coercing shouldn’t affect the result of 
> any ensuing dynamic casts.
> x as T as U === x as U.
> The interaction of these identities with the bridging conversions, as well as 
> with other type system features like implicit nonoptional-to-Optional 
> conversion, occasionally requires surprising behavior, for instance the 
> behavior of nil Optional values in https://github.com/apple/swift/pull/1949 
> <https://github.com/apple/swift/pull/1949>. These rules also inform the 
> otherwise-inconsistent use of as to perform explicit bridging conversions, 
> when as normally only forces implicit conversions. By simplifying the scope 
> of dynamic casts, it becomes easier to preserve these rules without bugs and 
> unfortunate edge cases.
> 
> The Abilities of as? Today
> 
> In discussing how to change the behavior of dynamic casts, it’s worth 
> enumerating all the things dynamic casts are currently able to do:
> 
> Check that an object is an instance of a specific class.
> 
> class Base {}; class Derived: Base {}
> 
> func isKindOfDerived(object: Base) -> Bool {
>   return object is Derived
> }
> 
> isKindOfDerived(object: Derived()) // true
> isKindOfDerived(object: Base()) // false
> Check that an existential contains an instance of a type.
> 
> protocol P {}
> extension Int: P {}
> extension Double: P {}
> 
> func isKindOfInt(value: P) -> Bool {
>   return value is Int
> }
> isKindOfInt(value: 0) // true
> isKindOfInt(value: 0.0) // false
> Check that a generic value is also an instance of a different type.
> 
> func is<T, U>(value: T, kindOf: U.Type) -> Bool {
>   return value is U
> }
> 
> is(value: Derived(), kindOf: Derived.self) // true
> is(value: Derived(), kindOf: Base.self) // true
> is(value: Base(), kindOf: Derived.self) // false
> is(value: 0, kindOf: Int.self) // true
> Check whether the type of a value conforms to a protocol, and wrap it in an 
> existential if so:
> 
> protocol Fooable { func foo() }
> 
> func fooIfYouCanFoo<T>(value: T) {
>   if let fooable = value as? Fooable {
>     return fooable.foo()
>   }
> }
> 
> extension Int: Fooable { func foo() { print("foo!") } }
> 
> fooIfYouCanFoo(value: 1) // Prints "foo!"
> fooIfYouCanFoo(value: "bar") // No-op
> Check whether a value is _ObjectiveCBridgeable to a class, or conversely, 
> that an object is _ObjectiveCBridgeable to a value type, and perform the 
> bridging conversion if so:
> 
> func getAsString<T>(value: T) -> String? {
>   return value as? String
> }
> func getAsNSString<T>(value: T) -> NSString {
>   return value as? NSString
> }
> 
> getAsString(value: "string") // produces "string": String
> getAsNSString(value: "string") // produces "string": NSString
> 
> let ns = NSString("nsstring")
> getAsString(value: ns) // produces "nsstring": String
> getAsNSString(value: ns) // produces "nsstring": NSString
> Check whether a value conforms to ErrorProtocol, and bridge it to NSError if 
> so:
> 
> enum CommandmentError { case Killed, Stole, GravenImage, CovetedOx }
> 
> func getAsNSError<T>(value: T) -> NSError? {
>   return value as? NSError
> }
> 
> getAsNSError(CommandmentError.GravenImage) // produces bridged NSError
> This is what enables the use of catch let x as NSError pattern matching to 
> catch Swift errors as NSErrorobjects today.
> 
> Check whether an NSError object has a domain and code matching a type 
> conforming to _ObjectiveCBridgeableErrorProtocol, and extracting the Swift 
> error if so:
> 
> func getAsNSCocoaError(error: NSError) -> NSCocoaError? {
>   return error as? NSCocoaError
> }
> 
> // Returns NSCocoaError.fileNoSuchFileError
> getAsNSCocoaError(error: NSError(domain: NSCocoaErrorDomain,
>                                  code: NSFileNoSuchFileError,
>                                  userInfo: []))
> Drill through Optionals. If an Optional contains some value, it is extracted, 
> and the cast is attempted on the contained value; the cast fails if the 
> source value is none and the result type is not optional:
> 
> var x: String? = "optional string"
> getAsNSString(value: x) // produces "optional string": NSString
> x = nil
> getAsNSString(value: x) // fails
> If the result type is also Optional, a successful cast is wrapped as some 
> value of the result Optional type. nil source values succeed and become nil 
> values of the result Optional type:
> 
> func getAsOptionalNSString<T>(value: T) -> NSString?? {
>   return value as? NSString?
> }
> 
> var x: String? = "optional string"
> getAsOptionalNSString(value: x) // produces "optional string": NSString?
> x = nil
> getAsOptionalNSString(value: x) // produces nil: NSString?
> Perform covariant container element checks and conversions for Array, 
> Dictionary, and Set.
> 
> There are roughly three categories of functionality intertwined here. (1) 
> through (4) are straightforward dynamic type checks. ((4) is arguably a bit 
> different from (1) through (3) in that protocol conformances are extrinsic to 
> a type, whereas (1) through (3) check the intrinsic type only of the 
> participating value.) (5) through (7) involve Cocoa bridging conversions. (8) 
> and (9) reflect additional implicit conversions supported by the language at 
> compile time into the runtime type system. Optional and covariant container 
> conversions have also been criticized as occasionally surprising and 
> inconsistent with the rest of the language. If we curtail these conversions 
> in the compiler, we would also want to consider removing their special 
> dynamic cast behavior too. For the purposes of this discussion, I’d like to 
> focus on removing the bridging behavior, cases (5) through (7).
> 
> Replacements for Dynamic Cast Behavior
> 
> If we remove bridging behavior from dynamic casts, we still need to provide 
> API for performing those conversions. I’d recommend introducing unlabeled 
> initializers for these conversions, matching the conventions for other 
> value-preserving conversions in the standard library:
> 
> 
> extension String {
>   init(_ ns: NSString)
> }
> extension NSString {
>   init(_ value: String)
> }
> extension Array where Element: _ObjectiveCBridgeable {
>   init(_ ns: NSArray<Element._ObjectiveCType>)
> }
> extension NSArray {
>   init<BridgedElement: _ObjectiveCBridgeable
>        where BridgedElement._ObjectiveCType == Element>(
>     _ value: Array<BridgedElement>)
> }
> /* etc. */
> 
> NSError bridging can also be extracted from the runtime, and the same 
> functionality exposed as a factory initializer on NSError:
> 
> 
> extension NSError {
>   init(_ swiftError: ErrorType)
> }
> 
> It’s also useful to be able to conditionally bridge to a value type from 
> AnyObject, especially when working with heterogeneous property lists from 
> Cocoa. This could be handled using failable initializers:
> 
> 
> extension String {
>   init?(bridging: AnyObject)
> }
> extension Array {
>   init?(bridging: AnyObject)
> }
> extension Dictionary {
>   init?(bridging: AnyObject)
> }
> /* etc. */
> 
> (This can probably be factored into a protocol extension on 
> _ObjectiveCBridgeable.) Similarly, one could add a failable initializer to 
> ErrorProtocol for bridging NSErrors back to Swift error values:
> 
> 
> extension ErrorType {
>   init?(bridging: NSError)
> }
> 
> If you want to get really reductionist, you can ask whether as? and related 
> operations really need special syntax at all; they could in theory be fully 
> expressed as global functions, or as extension methods on Any/AnyObject if we 
> allowed such things. Regardless, I think we want type-checking dynamic casts 
> to be clearly a different operation from these bridging conversions. This 
> will lead to a cleaner, easier-to-understand model with less special-case 
> magic behavior.
> 
> -Joe
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to