On Wed, Jan 20, 2016 at 4:28 PM Dave via swift-evolution < swift-evolution@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@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@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@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@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