[swift-evolution] [Proposal] Built-in "bridge" feature

Austin Zheng austinzheng at gmail.com
Thu Jan 21 11:55:16 CST 2016


I wouldn't be opposed to this if the 'as' was required (or there were some other syntax that prevented it from being implicit).

There are a few more questions that come up, but they have more to do with the specifics of the implementation:

- How would the compiler use the syntax to distinguish between different valid conversions (you briefly mentioned this)? What about conversions that you import from a different module? Would it be too limiting to restrict this to conversions that are defined in the same module (+ any in the stdlib)?
- How would the 'as' syntax (or even the non-'as' syntax) work with generic types? For example, Javi mentioned a possibly useful conversion from T --> Synchronized<T>. (Side note - might this be a use case for higher-kinded types?)

Best,
Austin

> On Jan 21, 2016, at 7:05 AM, Jerome ALVES <j.alves at me.com> wrote:
> 
> Well, If you think about it, this bridge feature how I presented it here does nothing more than what we can do today. Like error handling in Swift 2, it's just a syntactic sugar to standardize a commonly used pattern. Moreover if we decide to don't allow implicit conversion but only explicit conversions with the "as" keyword, we have a feature which increase clarity. 
> 
> Don't you find that in... 
> 
> func doSomethingWith(url: NSURL) {
>     ... 
> }
> 
> let urlString = "http://swift.org <http://swift.org/>"
> 
> ... this syntax :
> doSomethingWith(urlString as! NSURL) // we use as! instead of as because this bridge returns a NSURL? instead of NSURL
> 
> ... is more clear than this syntax :
> doSomethingWith(urlString.toURL()!)
> 
> Moreover, the IDE can bring some useful features here : We can make a cmd + click on the "as" keyword to jump right into the bridge implementation, or a option + click to see the doc associated to it.
> 
> 
> - Jérôme
> 
>> Le 21 janv. 2016 à 08:35, Austin Zheng <austinzheng at gmail.com <mailto:austinzheng at gmail.com>> a écrit :
>> 
>> I don't really like this feature; I see it as one of those "more brevity in exchange for less clarity" features that are an anti-goal of the project. If a bug is caused by a type conversion, I want to be able to narrow the offending conversion to an explicit call, not have to guess at which point the compiler decided to insert a conversion, which conversion was inserted, or whether one of my dependencies might be exporting a conversion I wasn't aware of.
>> 
>> There was also a thread some time ago where one of the developers stated that adding general implicit conversions to Swift would make building a performant type checker impossible, but I can't find it.
>> 
>> One major exception I can think of is QoL issues when working with numbers and arithmetic, for example automatically widening integers, but there's a separate project working on redesigning that aspect of the language.
>> 
>> Best,
>> Austin
>> 
>> 
>> 
>> On Wed, Jan 20, 2016 at 10:58 PM, Javier Soto via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> IIRC there was syntax to do this in Swift 1.0 which latter got removed from the language.
>> This sort of feature enables some cool patterns. I remember an example where some sort of Synchronized<T> object could be passed to a function expecting a T using this feature. However I can also see how implicit conversions can make code hard to read, and maybe they also cause complexity in the compiler (where suddenly every expression could potentially be evaluated as another type)
>> 
>> On Wed, Jan 20, 2016 at 4:28 PM Dave via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> I was thinking A as C would tell the compiler to try A bridge C, and failing that, look through the types to which A *can* bridge to see if any of them can bridge to C, and so on… Having thought about it more, though, that’s a horrible idea. It’d be way too easy to have some *very* subtle side effects if you bridge YourAwesomeType between a couple of types that were previously bridged by some other path (a change in rounding behavior comes to mind). This is especially true since there’s nothing preventing the bridges from being “lossy”… imagine “bridging” 0.0001 to YourAwesomeType (which stores it as an Int) before then bridging to some other type, where previously 0.0001 would’ve gotten there via some path that *didn’t* round it to an Int along the way.
>> 
>> Yeah, probably stick with having to be explicit about *how* you intend A to bridge to C, if it can’t just do it directly
>> 
>> - Dave Sweeris
>> 
>>> On Jan 20, 2016, at 15:49, Jerome ALVES <j.alves at me.com <mailto:j.alves at me.com>> wrote:
>>> 
>>> I'm not sure, do you suggest to always use the "as" keyword, or only in case where we want to move from "A" to "C" passing by "B" ?
>>> 
>>> In fact,I feel right about always needing to use the "as" keyword. It's true that this kind of feature could be too magical and cause some issues. Having to explicitly cast to bridged type could be a good compromise between readability and safety.
>>> 
>>> 
>>>> Le 21 janv. 2016 à 00:37, davesweeris at mac.com <mailto:davesweeris at mac.com> a écrit :
>>>> 
>>>> I could be wrong, but I believe that implicit type conversion, in general, causes problems (which is why ImplicitlyUnwrappedOptionals are handled with “compiler magic” as opposed to a general language feature). How would you feel about reusing the as keyword?
>>>> let a = A()
>>>> doSomethingWithC(a as C) // Compiler could check if there are explicit bridging functions, and fallback to as’s current meaning if not
>>>> 
>>>> Either way, though, I’d love a short-hand way to convert between types.
>>>> 
>>>> - Dave Sweeris
>>>> 
>>>>> On Jan 20, 2016, at 15:27, Jerome ALVES via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>> Hi everyone,
>>>>> 
>>>>> This is my first message on the mailing list and I hope I'll do everything right :)
>>>>> I've been through the mailing-list archive and I didn't see anything close to this proposal so I hope I didn't miss anything.
>>>>> Introduction
>>>>> 
>>>>> Sometimes, there are several ways to represent the same thing. For instance NSURL and NSURLComponents are both used to deal with URLs, but according to the API you want to use you must have to make manual conversions from one type to another. My proposal is to add a built-in bridge feature into the Swift language to remove a lot of boilerplate code and increase readability.
>>>>> Motivation
>>>>> 
>>>>> 1. It's a really convenient pattern 
>>>>> 
>>>>> Swift has several great solutions for types. Structs, Enums, and Protocols can do as much as Classes. You can define properties, methods, subscripts, you can extend them, etc.. This allowed developers to use some types where they weren't expected. For instance we can use an enum AppColors to define all colors used by an app, and as an enum can have properties, we can add a var color: UIColor on it to generate the associate UIColor (or NSColor)
>>>>> 
>>>>> Its really convenient but wherever we want to use this color, we need to call the .color property :
>>>>> myLabel.textColor = AppColors.PrimaryLabelTextColor.color
>>>>> 
>>>>> We can also have a protocol ColorConvertible defined like this to simplify things :
>>>>> protocol ColorConvertible {
>>>>>   var color: UIColor { get }
>>>>> }
>>>>> 
>>>>> Let's take a more concrete example with the open source library Alamofire (https://github.com/Alamofire/Alamofire <https://github.com/Alamofire/Alamofire>).
>>>>> Alamofire makes an extensive usage of this pattern to convert different types into a "NSURL-compatible" String or a NSURLRequest object.
>>>>> 
>>>>> Then the Alamofire API use only those URLStringConvertible and URLRequestConvertible protocols as method inputs.
>>>>> 
>>>>> It's great because at the right moment where you use the Alamofire API, no matter if you currently use a NSURL or a NSURLComponent, you can pass both as argument to the Alamofire function.
>>>>> 
>>>>> Moreover, you may want to use a custom type to build your URLs and validate them. This allows you to add some safety because you can use strong-typed enums as path components instead of error-prone strings.
>>>>> And here is where this pattern is convenient : you can make your custom type (which could be a class, a struct or an enum) conforming to URLStringConvertible and use it directly as Alamofire API functions input.
>>>>> 
>>>>> But this is sadly limited for Alamofire API. What about all other frameworks which only take NSURL as input ?
>>>>> 
>>>>> Using protocols for this is counterintuitive :
>>>>> 	– protocols are especially thought to don't have to deal with a particular type right ? But what we do here ? We use it only to convert the receiver into the desired type. After the conversion, the original receiver is not even used anymore because we can do nothing with it except convert it.
>>>>> 
>>>>> 	– we already can see different way to write these conversions,  : 
>>>>> 		- var URLString: String { get }  arbitrary var name pattern
>>>>> 		- var integerValue: Int { get }  _Value var name pattern (like in NSNumber)
>>>>> 		- func toColor() -> UIColor  to_() func name pattern 
>>>>> 
>>>>> 	– everytime we want to have a type conversion we need te create the associated protocol but it won't be compatible with third party libraries unless by manually write forwarding methods...
>>>>> 
>>>>> 
>>>>> 2. Swift language already makes convenient bridges between some Obj-C types and theirs Swift counterparts
>>>>> 
>>>>> I didn't take the time to see how it's currently implemented right now, but we already have bridging. For instance this code is valid without doing anything : 
>>>>> 
>>>>> func doSomethingWithNumber(number: NSNumber) {
>>>>>   //...
>>>>> }
>>>>> 
>>>>> let integer: Int = 42
>>>>> doSomethingWithNumber(integer)
>>>>>  
>>>>> Proposed solution
>>>>> 
>>>>> The cleanest way I see to add this feature is by having a new bridge keyword. A bridge could be implemented either into the type implementation scope or in an extension. Bridges can also be a protocol requirement and even have a default implementation thanks to protocol extensions. In fact, bridge keyword can be used in same places than the subscript keyword.
>>>>> 
>>>>> Here is what it could look like for the above NSURL example (note how the bridge could return an optional if needed) :
>>>>> 
>>>>> extension String {
>>>>>   bridge NSURL? {
>>>>>     return NSURL(string: self)
>>>>>   }
>>>>>   
>>>>>   bridge NSURLComponents? {
>>>>>     return NSURLComponents(string: self)
>>>>>   }
>>>>> }
>>>>> 
>>>>> extension NSURLComponents {
>>>>>   bridge NSURL? {
>>>>>     return self.URL
>>>>>   }
>>>>> }
>>>>> 
>>>>> Now this is how Swift-to-Foundation bridge could be implemented with this new syntax : 
>>>>> 
>>>>> extension NSNumber {
>>>>>   bridge Int {
>>>>>     return self.integerValue
>>>>>   }
>>>>> }
>>>>> 
>>>>> extension Int {
>>>>>   bridge NSNumber {
>>>>>     return NSNumber(integerValue: self)
>>>>>   }
>>>>> }
>>>>> 
>>>>> Then, as soon a bridge is defined from a type A to a type B, anywhere an API expects to have a type B, we could pass a type A instead :
>>>>> 
>>>>> enum AppColors {
>>>>>   case PrimaryTextColor
>>>>>   case SecondaryTextColor
>>>>>   
>>>>>   bridge UIColor {
>>>>>     switch self {
>>>>>     case .PrimaryTextColor:
>>>>>       return UIColor.blackColor()
>>>>>     case .SecondaryTextColor:
>>>>>       return UIColor.grayColor()
>>>>>     }
>>>>>   }
>>>>> }
>>>>> 
>>>>> ...
>>>>> 
>>>>> let cell = UITableViewCell(style: .Value1, reuseIdentifier: "MyCell")
>>>>> cell.textLabel?.textColor = .PrimaryTextColor
>>>>> cell.detailTextLabel?.textColor = .SecondaryTextColor
>>>>> 
>>>>> We could also imagine that bridges support error throwing : 
>>>>> 
>>>>> extension String {
>>>>>   enum ColorBridgeError: ErrorType {
>>>>>     case ColorNameNotSupported
>>>>>   }
>>>>>   bridge throws UIColor {
>>>>>     switch self {
>>>>>     case "red":
>>>>>       return UIColor.redColor()
>>>>>     case "blue":
>>>>>       return UIColor.blueColor()
>>>>>     default:
>>>>>       throw ColorBridgeError.ColorNameNotSupported
>>>>>     }
>>>>>   }
>>>>> }
>>>>> 
>>>>> ...
>>>>> 
>>>>> do {
>>>>>   cell.textLabel?.textColor = try self.colorNameTextField.text
>>>>>   self.errorLabel.text = nil
>>>>> }
>>>>> catch String.ColorBridgeError.ColorNameNotSupported {
>>>>>   self.errorLabel.text = "This color name is invalid :("
>>>>> }
>>>>> 
>>>>> This implementation is of course one of many implementations possible and I'm really open to suggestions. For instance I already can see one trade-off of this implementation : for the String -> NSURL? bridge, why NSURL(string: self) would be chosen over NSURL(fileURLWithPath: self) ? 
>>>>> 
>>>>> We could also image than bridge are "chainable" (but maybe it could affect compilation times ?). For instance 
>>>>> 
>>>>> extension A {
>>>>>   bridge B {
>>>>>     return B(withA: self)
>>>>>   }
>>>>> }
>>>>> 
>>>>> extension B {
>>>>>   bridge C {
>>>>>     return C(withB: self)
>>>>>   }
>>>>> }
>>>>> 
>>>>> func doSomethingWithC(anyC: C) {
>>>>>   //...
>>>>> }
>>>>> 
>>>>> let a = A()
>>>>> doSomethingWithC(a) // Compiler could implicitly bridge `a` to type `B`, then bridge the result to type `C`
>>>>> 
>>>>> But this is optional for the feature, we could imagine to explicitly need to implement a bridge from A to C.
>>>>> 
>>>>> 
>>>>> 
>>>>> 
>>>>> Well, I think that's all for now. I hope it was not too long to read and it was well explained. I'm of course open to all suggestions, questions, enhancements, etc...
>>>>> 
>>>>> I would also be happy to have technical details about possible implementations as I'm not an expert at all in compiler design. I really don't know how this feature could affect the compiler and any insight here will be welcome.
>>>>> 
>>>>> Thanks for reading.
>>>>> 
>>>>> Cheers,
>>>>> 
>>>>> Jérôme Alves
>>>>> 
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> -- 
>> Javier Soto 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160121/66c6611d/attachment.html>


More information about the swift-evolution mailing list