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
