Pitch: Allow interchangeable use of `CGFloat` and `Double` types

In addition to this just being a ton of work, here are two more reasons why I don't think real overloads are the best answer:

  • New APIs aren't backward deployable, unless you want all of them in your app binary
  • Overloads are worse for type checker performance than a targeted implicit conversion. Though both are decisions that the type checker needs to make, with respect to a function call with a CGFloat argument, an implicit conversion is limited to a single argument, whereas trying another overload would cause the type checker to redo all of the work for all of the other arguments too.
7 Likes

My view on this is that a new unary operator for performing these types of conversions could work well. As someone mentioned in this thread, Swift already has many features specifically for Objective-C interoperability, and I would see such an operator as being somewhat analogous, but for certain Apple APIs instead of for Objective-C in general.

This operator could probably be implemented as just some new Swift code, without any changes to the language itself. It would be a good idea to include it as a part of the standard library, though.

The fact that itā€™s an explicit operator means that we donā€™t have to compromise on not allowing implicit conversions, but it still fixes the ergonomic issues. Furthermore, this operator could be overloaded for other ā€œannoyingā€ conversions between ā€œregularā€ and SDK-specific types. I have no doubt that there would be lively debate on which such overloads to include in the standard library, but I would still see that as an improvement, and I think that most people would support its implementation for converting between Double and CGFloat.

This solution sidesteps the issues with having different language behavior on 32- and 64- bit platforms because itā€™s just syntactic sugar for good olā€™ Double(ā€¦) and CGFloat(ā€¦) initializer invocations.

Itā€™s true that each programmer could, in theory, just implement their own version of this operator in each project, but I think that thereā€™s value in standardizing it at least at the standard-library level.

Ergonomics doesn't just mean the verbosity of the code. With an explicit unary operator (which would also be worse for type checker performance if its overloaded), a developer would still go through the same process of writing out the code, getting an error message, adding the explicit operator. Then later on, factoring out part of their expression into a separate one, not realizing that the result of type inference changed due to 1.0 defaulting to Double, getting another error in an expression that uses that Double in combination with CGFloat, adding the unary operator, and so on...

4 Likes

Thatā€™s a good point. However, Iā€™m personally hesitant to support a language change for just one specific type conversion, especially when that change introduces implicit behavior that isnā€™t fully in line with the existing philosophy. I do think that a unary operator would be better than what we currently have, but I understand that it doesnā€™t go as far in terms of ergonomics as many people seem to want.

In terms of solving the problem without requiring any explicit syntax, I would probably favor the ā€œretroactive type aliasā€ solution because it deviates minimally from the behavior of existing language features (as opposed to true implicit conversion).

so you have to cast, there is nothing that can eliminate the need to cast Int here. This proposal will not help this case

Correct, I was just pointing out the pain points I hit in this area (compared to Objective-C, where the type laxness helps here). Fixing CGFloat :: Double would help, and I support the pitch, but there'd still be other annoyances of the same ilk.

1 Like

Broadly sympathetic.

Is there any risk of breaking equality (I know floating point equality is generally a bad idea)?

let a: CGFloat = x
let b: Double = x

a == b // Should always be true

Will this ever not hold under this proposal in all cases of x being CGFloat or Double where CGFloat is 32 bit.

Not necessarily a deal breaker but worth considering.

Double is a preferred type here so it would widen CGFloat and it should always be true.

It is most fully expressed under ā€œAlternatives Consideredā€; as evidenced by a number of follow-up comments to this thread, including @migueldeicazaā€™s, others have read the text in the same way. But more generally, any reference to ā€œimplicit conversionā€ between numeric types in the text (and in the implementing PR) reinforce this impression.

@hborla and @xedin, tell me if this is an outlandish idea:

@scanon suggests considering the feature as ā€œa tool that effectively implicitly adds a shadow of that API [that uses CGFloat] that uses Double instead.ā€

Can we model this feature in a way analogous to property wrappers? That is, suppose we treated a parameter of type CGFloat as though itā€™s a property wrapper parameter which takes a Double argument and ā€œwrapsā€ it, and a local variable of type CGFloat similarly. Would that model work (conceptually; it would not need to be implemented exactly so, of course) or totally fall apart somewhere?

That text is explicit in suggesting the ground proved is in the capability of the typechecker, and that the potential future direction would be only about widening. But future directions aren't locked without further proposals so this seems like it's not very solid grounds for opposing what is being proposed here. That said, there is a lot of support for the concept of a generalized widening feature, and it merits further discussion (if maybe in break-out threads), and the proving ground in the typechecker is useful information to include.

Why would this be better?

Again, what Iā€™m concerned about is that the user-facing addition to the language being proposed here is explained as an implicit conversion between numeric types, and the text explicitly discusses that it could be the first of many. I believe that the justification for improving the ergonomics of using CGFloat is there, but that it does not justify an ā€œimplicit conversion.ā€

Each proposal can add not just a concrete proposed feature but can also herald a new direction of language evolution. We are exhorted explicitly to consider the direction of Swift in reviews. Clearly, the authors of this pitch want to promote a certain direction; otherwise why include the text? I am voicing the opposing view on that direction. We donā€™t have to talk about it here in this thread: the language can be taken out of the pitch text and we can move on. But so long as the authors write about it, it is on-topic for this thread and grounds for discussion and a piece of what to evaluate for a proposal.

So, on that note, I find the ā€œimplicit overloadā€ model to be more consonant within the existing ā€œplatonic idealā€ model of Swift.

I am in favor of improving the ergonomics of using CGFloat without incorporating the concept of ā€œimplicit conversionsā€ into the language. Therefore, to me, this would be better because it would allow the former to be accomplished without the latter, without resorting to totally ad hoc rules for a single type but rather building on the rules for a feature we already have.

There are already a number of targeted implicit conversions in the compiler specifically for improving the ergonomics of interoperability with Objective-C APIs. For example, there are CFType <-> NSType conversions just like the one being proposed here.

7 Likes

Right: as I said earlier, itā€™s widely known and accepted that there are special interoperability features, including type system accommodations, for Objective-C APIs.

With respect to what Iā€™m voicing concern about, itā€™s the discussed direction of incorporating this particular pitch as the first of many implicit conversions among numeric types to be designed as part of the language proper, not as an interoperability concern.

But what you're proposing instead seems, at least to me, to be a significantly more complicated both in terms of the user-facing model (bear in mind that property wrappers are one of our hardest-to-understand features, amongst some heavy competition) and in terms of compiler complexity. So it really feels like this is over-prioritizing a principle to the detriment of the overall result.

5 Likes

I think thatā€™s a fair enough perspective (that property wrappers are too complicated a feature)ā€”again, Iā€™m only speaking to how we conceptualize this proposed ergonomic improvement in terms of user-facing language rules, not how itā€™s implemented under the hood.

If thatā€™s not the right direction to take, Iā€™m fully satisfied with a model where CGFloat to Double conversion is regarded as a special-case interoperability rule in the same vein as CFType to NSType, as @hborla writes.

2 Likes

The conversions being proposed are explicitly not desirable in the general case. CGFloat needs an implicit narrowing conversion in order to solve the situation with 32-bit devices. For this reason, a very custom solution for that type and problem domain is being proposed.

As we've seen, there is a lot to debate over whether an implicit widening feature would be a good direction for the language, and how exactly it could work. That's not something being proposed here because regardless of whether it ends up being accepted or not, the CGFloat issue needs to be addressed, and would not be addressed by the general feature even if it were adopted at some point in the future.

This is how the feature is implemented and presented in this pitch - Proposed Solution talks about a typealias and solver implements a special restriction only between Double and CGFloat types, just like it does specifically for CFType and NSType.

Since you mention that "implicit conversion" is used in the source - it's a good convenience to have an element of the locator path that has common name and stores a kind of conversion that is happening, I prefer doing that instead of creating N locator path elements to represent each conversion or restriction (the way the called in the code), but I could change that if that's the only naming concern i.e. I can change path element name to restriction or something similar.