Proposal: Auto-convert for numbers when safe


(Jon Hull) #1

I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

On a side note, can we get a version of floor() which returns an Int (or have I missed that somewhere)?

Thanks,
Jon


(Brent Royal-Gordon) #2

I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

Well, certain large Ints may not have a corresponding Float, and very large ones (on 64-bit platforms) might not have an exact Double. In fact, even Float -> Double conversions can go subtly wrong, as I recall.

One reason not to do this is that I suspect it may slow down type inference, and thus the compiler. If every numeric type can be implicitly converted to a long list of larger types, that’s a lot more types for the compiler to think about every time you add two numbers.

Another is simply that it’s not clear how you would express to the type system which conversions could be done implicitly and which were verboten. Remember, Int and friends are all ordinary Swift structs; we don’t want to give them magical powers that aren’t available to other types. (I seem to recall that in the early Swift betas, there was a magic _convert() method or something that you could overload, but this was dropped as the language was redesigned. Perhaps the people who actually did the dropping could explain that one.)

A third is that conversions can, in some cases, cost time. Usually not much, but in a tight arithmetic loop it might matter.

A fourth: your code should probably not be using so many numeric types. There’s rarely a good reason to use an explicitly-sized integer, an unsigned integer, or a Float. That means most of your code should be using only Int, Double, and CGFloat (which is unavoidable). If you’re converting because an awkwardly-bridged API uses an explicitly sized type, overload the API with a wrapper that’s correctly typed. If you’re converting because you’re doing bit-twiddling, you probably need fine control over when values are resized.

Finally, if all that fails and you do need to convert, I prefer the strategy of marking your conversion sites explicitly. This (old, possibly out-of-date) code sample shows what I mean: <https://gist.github.com/brentdax/f85ede7dd7b26c6e716e> Marking conversions explicitly with a low-visual-impact operator which only allows widening conversions gets you the same safety as your proposal and nearly the same convenience, but also makes your meaning explicit, is kinder to the type checker, and doesn’t require any new features to be added to the type system.

···

--
Brent Royal-Gordon
Architechies


(Pyry Jahkola) #3

For example:
• Int -> Float, Double, or CGFloat (but not the other way)

These are not "safe" conversions in general. A Float can only hold integral values up to 2^24, and Double a only up to 2^53. Neither can fit 64-bit integers and the former not even Int32.

On a side note, can we get a version of floor() which returns an Int (or have I missed that somewhere)?

This would be nice. As would be similar versions of ceil and round.

— Pyry Jahkola

···

On 05 Dec 2015, at 14:27, Jonathan Hull <jhull@gbis.com> wrote:


(Chris Lattner) #4

I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.

The problem with doing this today is that there are a lot of dependencies we need to get resolved first.

1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.

2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.

3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.

There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.

It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.

-Chris

···

On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:

I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?


(Manav Gabhawala) #5

One suggestion of a way to get around the problem of promotions could be implicit initializers. So you could have one parameter initializers that can be marked with a keyword/attribute like implicit and the compiler can automatically insert the right the initializer in place (kind of like C++ does it except the keyword implicit would be an antonym to the explicit keyword in C++). This could potentially solve the type checker speed and also allow you to move this implementation to the stdlib. It could also potentially have some interesting use cases outside of the std library too. We would also have to consider whether implicit initializers could throw errors and whether they could be failable or not.
IMO implicit initializers should not be able to throw but should be allowed to be failable. For instance, one could have an implicit initializer from a String to NSURL but have it failable too and use a guard let/if let to bind to it (see example later)

So something along the lines of:

class Double {
  implicit init(_ f: Float) {
    // initializes a double from a float.
  }
}

// For the String -> NSURL
class NSURL {
  implicit init?(_ str: String) {
    // initializes the usual way.
  }
}
// And then in at the call site
guard let URL : NSURL = someString
else { return }
// Do something with someString.

However, because of how Swift is structured and its emphasis on being explicit (which is a great thing), this should only be used sparingly and should be discouraged unless it absolutely makes sense.

Regards,
Manav Gabhawala

I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.

The problem with doing this today is that there are a lot of dependencies we need to get resolved first.

1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.

2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.

3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.

There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.

It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.

-Chris

···

On December 6, 2015 at 2:02:51 AM, Chris Lattner via swift-evolution (swift-evolution@swift.org) wrote:
On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #6

I would be happy with any solution that provides failable conversion so I would support that as well.

Any chance we could see something like this in Swift 2.2 or Swift 3? I was hoping the failable initializers might be low hanging fruit but it seems like the scoped feature would be more than that.

···

Sent from my iPad

On Dec 6, 2015, at 4:04 PM, Chris Lattner <clattner@apple.com> wrote:

Could definitely be interesting. I’d personally like to turn it into a scoped feature along the lines of this post though:

https://lists.swift.org/pipermail/swift-evolution/2015-December/000292.html

-Chris

On Dec 6, 2015, at 5:29 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Related to this, but maybe easier to do in the short term: what do you think of adding failable overloads to the numeric conversion initializers Chris?

They would throw or return nil if the runtime value could not be preserved rather than trap, truncate, etc. Floating point types might allow a tolerance for small value changes due to precision, although I'm not sure if that would be good or not. These initializers would be very useful when processing data from an external source such as JSON that has an expected type but cannot be trusted.

Sent from my iPad

On Dec 6, 2015, at 1:02 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:
I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.

The problem with doing this today is that there are a lot of dependencies we need to get resolved first.

1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.

2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.

3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.

There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.

It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #7

One suggestion of a way to get around the problem of promotions could be implicit initializers.

Swift has already had a feature to support arbitrary implicit conversion between types: it was madness and got ripped out :-)

I’m not opposed to allowing user-defined implicit conversions, but IMO they need to be limited to a DAG of subtype relationships.

-Chris

···

On Dec 5, 2015, at 11:21 PM, Manav Gabhawala <manav1907@gmail.com> wrote:

So you could have one parameter initializers that can be marked with a keyword/attribute like implicit and the compiler can automatically insert the right the initializer in place (kind of like C++ does it except the keyword implicit would be an antonym to the explicit keyword in C++). This could potentially solve the type checker speed and also allow you to move this implementation to the stdlib. It could also potentially have some interesting use cases outside of the std library too. We would also have to consider whether implicit initializers could throw errors and whether they could be failable or not.
IMO implicit initializers should not be able to throw but should be allowed to be failable. For instance, one could have an implicit initializer from a String to NSURL but have it failable too and use a guard let/if let to bind to it (see example later)

So something along the lines of:

class Double {
  implicit init(_ f: Float) {
    // initializes a double from a float.
  }
}

// For the String -> NSURL
class NSURL {
  implicit init?(_ str: String) {
    // initializes the usual way.
  }
}
// And then in at the call site
guard let URL : NSURL = someString
else { return }
// Do something with someString.

However, because of how Swift is structured and its emphasis on being explicit (which is a great thing), this should only be used sparingly and should be discouraged unless it absolutely makes sense.

Regards,
Manav Gabhawala

On December 6, 2015 at 2:02:51 AM, Chris Lattner via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) wrote:

On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:
> I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.
>
> …but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).
>
>
> I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).
>
> For example:
> • If an Int is casting to a larger size (Int16 -> Int32)
> • Float -> Double
> • Float -> CGFloat
> • Int -> Float, Double, or CGFloat (but not the other way)
>
> I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.

The problem with doing this today is that there are a lot of dependencies we need to get resolved first.

1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.

2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.

3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.

There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.

It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Manav Gabhawala) #8

I created a pull request with the proposal: Implicit Initializer proposal (https://github.com/apple/swift-evolution/pull/37). Feel free to respond with suggestions/ideas with improvements to the proposal and things that may be missing.

Regards,
Manav Gabhawala

I would be happy with any solution that provides failable conversion so I would support that as well.

Any chance we could see something like this in Swift 2.2 or Swift 3? I was hoping the failable initializers might be low hanging fruit but it seems like the scoped feature would be more than that.

···

On December 6, 2015 at 5:33:37 PM, Matthew Johnson via swift-evolution (swift-evolution@swift.org) wrote:

Sent from my iPad

On Dec 6, 2015, at 4:04 PM, Chris Lattner <clattner@apple.com> wrote:

Could definitely be interesting. I’d personally like to turn it into a scoped feature along the lines of this post though:

https://lists.swift.org/pipermail/swift-evolution/2015-December/000292.html

-Chris

On Dec 6, 2015, at 5:29 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Related to this, but maybe easier to do in the short term: what do you think of adding failable overloads to the numeric conversion initializers Chris?

They would throw or return nil if the runtime value could not be preserved rather than trap, truncate, etc. Floating point types might allow a tolerance for small value changes due to precision, although I'm not sure if that would be good or not. These initializers would be very useful when processing data from an external source such as JSON that has an expected type but cannot be trusted.

Sent from my iPad

On Dec 6, 2015, at 1:02 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:
I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.

…but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).

I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).

For example:
• If an Int is casting to a larger size (Int16 -> Int32)
• Float -> Double
• Float -> CGFloat
• Int -> Float, Double, or CGFloat (but not the other way)

I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?

I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.

The problem with doing this today is that there are a lot of dependencies we need to get resolved first.

1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.

2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.

3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.

There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.

It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.

-Chris

_______________________________________________
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


(Douglas Gregor) #9

I created a pull request with the proposal: Implicit Initializer proposal <https://github.com/apple/swift-evolution/pull/37> (https://github.com/apple/swift-evolution/pull/37). Feel free to respond with suggestions/ideas with improvements to the proposal and things that may be missing.

I have two comments. The first is on the suitability of this proposal for Swift as a language; the second is about technical feasibility of the proposal.

Regarding suitability for Swift: for the most part, Swift’s type system has a set of subtyping relationships that allow implicit conversions (during compilation) that can be safely reversed via conditional downcasting (the “as?” operator). For example, say I have:

  protocol P { }
  struct X : P { }

I can turn an X into a P:

  var p: P = X()

and then reverse the operation with a cast:

  if let x = p as? X { … }

It’s a rather beautiful and powerful symmetry to the language. If we add implicit conversions:

  struct X {
    implicit init(_ y: Y) { … }
  }

  struct Y { }

  var x: X = Y()

Should we allow

  let y = x as? Y { … }

to work? If no, we’ve lost some useful symmetry to the type system.

If yes, we first need to figure out how the user can implement it… perhaps some opposite operation that can fail:

  extension Y {
    init?(castingFrom: X) { … }
  }

but how can we implement this in an efficient manner in the runtime? We would effectively have to represent the DAG of implicit conversion relationships in runtime data structures and evaluate those with every “as?” cast, which implies a heavy runtime burden for using this feature.

On technical feasibility: introducing user-defined conversions is a major complication to the type system, particularly in a language with general type inference. We did go down this route (there are vestiges of it still remaining in the expression type checker), and it added a huge amount of complexity to the type checker that compromised it’s ability to provide good type inference. We would need a massive improvement to the expression type checker for this to become technically feasible, and I’m not sure we even can get there. Swift has a complicated type system already—general type inference, parametric polymorphism, ad hoc overloading, general subtyping—and the type checker is one of the weaker areas of the implementation in part because of that complexity.

I don’t think we can bring a proposal up for review when there is a significant chance that it cannot be faithfully implemented. Obviously, asking you to “go implement a revolutionary new type checker” isn’t a great response, either, which leaves us in the unfortunate position of looking for some kind of assurance that, should we decide we want to go in this direction, we actually *can* go in this direction.

  - Doug

···

On Dec 6, 2015, at 11:48 PM, Manav Gabhawala via swift-evolution <swift-evolution@swift.org> wrote:

Regards,
Manav Gabhawala

On December 6, 2015 at 5:33:37 PM, Matthew Johnson via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) wrote:

I would be happy with any solution that provides failable conversion so I would support that as well.

Any chance we could see something like this in Swift 2.2 or Swift 3? I was hoping the failable initializers might be low hanging fruit but it seems like the scoped feature would be more than that.

Sent from my iPad

> On Dec 6, 2015, at 4:04 PM, Chris Lattner <clattner@apple.com> wrote:
>
> Could definitely be interesting. I’d personally like to turn it into a scoped feature along the lines of this post though:
>
> https://lists.swift.org/pipermail/swift-evolution/2015-December/000292.html
>
> -Chris
>
>> On Dec 6, 2015, at 5:29 AM, Matthew Johnson <matthew@anandabits.com> wrote:
>>
>> Related to this, but maybe easier to do in the short term: what do you think of adding failable overloads to the numeric conversion initializers Chris?
>>
>> They would throw or return nil if the runtime value could not be preserved rather than trap, truncate, etc. Floating point types might allow a tolerance for small value changes due to precision, although I'm not sure if that would be good or not. These initializers would be very useful when processing data from an external source such as JSON that has an expected type but cannot be trusted.
>>
>>
>>
>> Sent from my iPad
>>
>>>> On Dec 6, 2015, at 1:02 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:
>>>>
>>>> On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:
>>>> I understand why you can’t auto-convert from a Double to a Float or Int32 to Int8. It is good that we have to add the cast explicitly and think though the implications.
>>>>
>>>> …but I don’t think through the implications because we currently have a boy who cried wolf situation where we have to explicitly cast everything (even the safe stuff).
>>>>
>>>>
>>>> I think all of the numeric types should be able to auto-convert if the conversion is safe (without loss of precision or overflow).
>>>>
>>>> For example:
>>>> • If an Int is casting to a larger size (Int16 -> Int32)
>>>> • Float -> Double
>>>> • Float -> CGFloat
>>>> • Int -> Float, Double, or CGFloat (but not the other way)
>>>>
>>>> I don’t see why these aren’t allowed. The forced casts make my code much less readable. Are the casts above dangerous in a way I am not aware of?
>>>
>>> I agree that the current Swift numerics model is suboptimal, I personally would like to see small integers implicitly promote to large integers (when they are known lossless), have Float promote to Double, and have both Float and Double promote to CGFloat (yes, I know that the Double -> CGFloat promotion would be lossy on 32-bit apple platforms). I personally don’t think that integer -> floating point promotions are a good idea even if value preserving, since their domains are so different.
>>>
>>> The problem with doing this today is that there are a lot of dependencies we need to get resolved first.
>>>
>>> 1. The type checker is really slow, partially because of too-many and too-crazy implicit conversions. We also get very surprising behavior when they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T to T? conversions. We have thoughts on this, but should be discussed in a separate thread if you’re interested.
>>>
>>> 2. These promotions should be expressible in the library, not hard coded into the compiler. This means that we would need a language feature to (e.g.) be able to define subtype relationships between structs. Such a feature would be generally useful and could allow us to push some of our existing compiler magic out to the stdlib.
>>>
>>> 3. We want the existing work to revise the numerics protocols to be better understood and hopefully implemented.
>>>
>>> There are also a ton of unrelated specific problems that should be addressed in various ways: e.g. macros like M_PI get imported as Double instead of a typeless literal, forcing tons of casts in code that wants to use it (e.g.) with Floats. These issues are separable, and blocked on things like generic properties not being in place.
>>>
>>> It would be great for interested contributors to start pushing on any of the above issues to help unblock progress on improving the numerics model.
>>>
>>> -Chris
>>>
>>> _______________________________________________
>>> 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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Manav Gabhawala) #10

Thanks for your feedback Doug.

With regards to your first concern, I think that we won't actually lose any
symmetry because if the user actually wants backward conversions they will
define it themselves and so we won't ever have the case where we are giving
this behavior for "free" and so the compiler will only have to do a one
level check of an implicit conversion. And while we want to support the
castingFrom implicit initializer (which can be failable if need be) it will
always be user defined and never generated by a protocol extension or the
compiler so things will still be Swifty.

From a technical standpoint I can't say much because I have no compiler

experience whatsoever (yet). But I can see how this might mess around with
the type checker and I can understand why this might be too difficult to
implement for swift 3. However, I strongly believe the current numbers
model of explicit conversion is too verbose and very non swift like. One of
the biggest problems is the way that Int types are imported from C
libraries and you have to keep converting back and forth between for
example a UInt32 and an Int or something similar. Would you be open to
restricting the feature to only work with number types and "hard code" that
behavior into the compiler for swift 3 or do you think that's something we
should not proceed with, or have a different solution for solving the
numbers problem?

Regards,
Manav Gabhawala

···

On December 12, 2015 at 1:57:10 AM, douglas gregor (dgregor@apple.com) wrote:

On Dec 6, 2015, at 11:48 PM, Manav Gabhawala via swift-evolution < > swift-evolution@swift.org> wrote:

I created a pull request with the proposal: Implicit Initializer proposal
<https://github.com/apple/swift-evolution/pull/37> (
https://github.com/apple/swift-evolution/pull/37). Feel free to respond
with suggestions/ideas with improvements to the proposal and things that
may be missing.

I have two comments. The first is on the suitability of this proposal for
Swift as a language; the second is about technical feasibility of the
proposal.

Regarding suitability for Swift: for the most part, Swift’s type system
has a set of subtyping relationships that allow implicit conversions
(during compilation) that can be safely reversed via conditional
downcasting (the “as?” operator). For example, say I have:

protocol P { }
struct X : P { }

I can turn an X into a P:

var p: P = X()

and then reverse the operation with a cast:

if let x = p as? X { … }

It’s a rather beautiful and powerful symmetry to the language. If we add
implicit conversions:

struct X {
  implicit init(_ y: Y) { … }
}

struct Y { }

var x: X = Y()

Should we allow

let y = x as? Y { … }

to work? If no, we’ve lost some useful symmetry to the type system.

If yes, we first need to figure out how the user can implement it… perhaps
some opposite operation that can fail:

extension Y {
  init?(castingFrom: X) { … }
}

but how can we implement this in an efficient manner in the runtime? We
would effectively have to represent the DAG of implicit conversion
relationships in runtime data structures and evaluate those with every
“as?” cast, which implies a heavy runtime burden for using this feature.

On technical feasibility: introducing user-defined conversions is a major
complication to the type system, particularly in a language with general
type inference. We did go down this route (there are vestiges of it still
remaining in the expression type checker), and it added a huge amount of
complexity to the type checker that compromised it’s ability to provide
good type inference. We would need a massive improvement to the expression
type checker for this to become technically feasible, and I’m not sure we
even can get there. Swift has a complicated type system already—general
type inference, parametric polymorphism, ad hoc overloading, general
subtyping—and the type checker is one of the weaker areas of the
implementation in part because of that complexity.

I don’t think we can bring a proposal up for review when there is a
significant chance that it cannot be faithfully implemented. Obviously,
asking you to “go implement a revolutionary new type checker” isn’t a great
response, either, which leaves us in the unfortunate position of looking
for some kind of assurance that, should we decide we want to go in this
direction, we actually *can* go in this direction.

- Doug

Regards,
Manav Gabhawala

On December 6, 2015 at 5:33:37 PM, Matthew Johnson via swift-evolution ( > swift-evolution@swift.org) wrote:

I would be happy with any solution that provides failable conversion so I
would support that as well.

Any chance we could see something like this in Swift 2.2 or Swift 3? I was
hoping the failable initializers might be low hanging fruit but it seems
like the scoped feature would be more than that.

Sent from my iPad

> On Dec 6, 2015, at 4:04 PM, Chris Lattner <clattner@apple.com> wrote:
>
> Could definitely be interesting. I’d personally like to turn it into a
scoped feature along the lines of this post though:
>
>
https://lists.swift.org/pipermail/swift-evolution/2015-December/000292.html
>
> -Chris
>
>> On Dec 6, 2015, at 5:29 AM, Matthew Johnson <matthew@anandabits.com> > wrote:
>>
>> Related to this, but maybe easier to do in the short term: what do you
think of adding failable overloads to the numeric conversion initializers
Chris?
>>
>> They would throw or return nil if the runtime value could not be
preserved rather than trap, truncate, etc. Floating point types might allow
a tolerance for small value changes due to precision, although I'm not sure
if that would be good or not. These initializers would be very useful when
processing data from an external source such as JSON that has an expected
type but cannot be trusted.
>>
>>
>>
>> Sent from my iPad
>>
>>>> On Dec 6, 2015, at 1:02 AM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:
>>>>
>>>> On Dec 5, 2015, at 4:27 AM, Jonathan Hull <jhull@gbis.com> wrote:
>>>> I understand why you can’t auto-convert from a Double to a Float or
Int32 to Int8. It is good that we have to add the cast explicitly and think
though the implications.
>>>>
>>>> …but I don’t think through the implications because we currently have
a boy who cried wolf situation where we have to explicitly cast everything
(even the safe stuff).
>>>>
>>>>
>>>> I think all of the numeric types should be able to auto-convert if
the conversion is safe (without loss of precision or overflow).
>>>>
>>>> For example:
>>>> • If an Int is casting to a larger size (Int16 -> Int32)
>>>> • Float -> Double
>>>> • Float -> CGFloat
>>>> • Int -> Float, Double, or CGFloat (but not the other way)
>>>>
>>>> I don’t see why these aren’t allowed. The forced casts make my code
much less readable. Are the casts above dangerous in a way I am not aware
of?
>>>
>>> I agree that the current Swift numerics model is suboptimal, I
personally would like to see small integers implicitly promote to large
integers (when they are known lossless), have Float promote to Double, and
have both Float and Double promote to CGFloat (yes, I know that the Double
-> CGFloat promotion would be lossy on 32-bit apple platforms). I
personally don’t think that integer -> floating point promotions are a good
idea even if value preserving, since their domains are so different.
>>>
>>> The problem with doing this today is that there are a lot of
dependencies we need to get resolved first.
>>>
>>> 1. The type checker is really slow, partially because of too-many and
too-crazy implicit conversions. We also get very surprising behavior when
they kick in. Specifically, IMO, we need to reevaluate the T! <-> T and T
to T? conversions. We have thoughts on this, but should be discussed in a
separate thread if you’re interested.
>>>
>>> 2. These promotions should be expressible in the library, not hard
coded into the compiler. This means that we would need a language feature
to (e.g.) be able to define subtype relationships between structs. Such a
feature would be generally useful and could allow us to push some of our
existing compiler magic out to the stdlib.
>>>
>>> 3. We want the existing work to revise the numerics protocols to be
better understood and hopefully implemented.
>>>
>>> There are also a ton of unrelated specific problems that should be
addressed in various ways: e.g. macros like M_PI get imported as Double
instead of a typeless literal, forcing tons of casts in code that wants to
use it (e.g.) with Floats. These issues are separable, and blocked on
things like generic properties not being in place.
>>>
>>> It would be great for interested contributors to start pushing on any
of the above issues to help unblock progress on improving the numerics
model.
>>>
>>> -Chris
>>>
>>> _______________________________________________
>>> 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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #11

Thanks for your feedback Doug.

With regards to your first concern, I think that we won't actually lose any symmetry because if the user actually wants backward conversions they will define it themselves

Right now, our subtype-to-supertype conversions are reversible. I think it would be unfortunate to lose that property, and I don’t believe it’s feasible to maintain that property in a language with user-defined conversions without an unacceptable runtime performance penalty.

and so we won't ever have the case where we are giving this behavior for "free" and so the compiler will only have to do a one level check of an implicit conversion. And while we want to support the castingFrom implicit initializer (which can be failable if need be) it will always be user defined and never generated by a protocol extension or the compiler so things will still be Swifty.

I realize that my second example didn’t demonstrate the issue properly, so I’ll try again. Here’s the example:

  struct X {
    implicit init(_ y: Y) { … }
  }

  struct Y {
    init?(castingFrom: X) { … }
  }

  var x: X = Y()
  var any: Any = x
  let y = any as? X { } // this is the interesting bit

Having reversible conversions implies that this as? cast should succeed, but how would that happen? The Swift runtime would need to query the dynamic type of “any” (it’s an X), then follow the runtime-generated DAG of conversions (backwards), calling init?(castingFrom:) at the appropriate points to attempt the cast. That’s complicated and bound to be slow.

From a technical standpoint I can't say much because I have no compiler experience whatsoever (yet). But I can see how this might mess around with the type checker and I can understand why this might be too difficult to implement for swift 3. However, I strongly believe the current numbers model of explicit conversion is too verbose and very non swift like. One of the biggest problems is the way that Int types are imported from C libraries and you have to keep converting back and forth between for example a UInt32 and an Int or something similar. Would you be open to restricting the feature to only work with number types and "hard code" that behavior into the compiler for swift 3 or do you think that's something we should not proceed with, or have a different solution for solving the numbers problem?

I understand the problem you’re trying to solve, and I don’t have a good solution to it. However, generalized user-defined conversions is, for me, too big a hammer for solving this particular problem.

  - Doug

···

On Dec 12, 2015, at 12:03 AM, Manav Gabhawala <manav1907@gmail.com> wrote: