[swift-evolution] [Proposal] Built-in "bridge" feature
Austin Zheng
austinzheng at gmail.com
Thu Jan 21 01:35:39 CST 2016
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> 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> 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> 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 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> 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).
>> 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
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
> --
> Javier Soto
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160120/1700f3d0/attachment-0001.html>
More information about the swift-evolution
mailing list