Failable arithmetic


(Brent Royal-Gordon) #1

Currently, Swift has three ways to handle potential overflows and other errors in arithmetic:

  // 1: Crashes
  Int.max + 1

  // 2: Returns the wrong answer (Int.min in this case)
  Int.max &+ 1

  // 3: Returns a tuple with the value of &+ and a boolean indicating whether it overflowed
  Int.addWithOverflow(Int.max, 1)

The problem is, if you want to handle overflows in some simple way, none of these are very good. 1 terminates your app, 2 gives the wrong answer, and 3 is very awkward to use. If you’re, for instance, working with numbers input by the user or downloaded from the Internet, you don’t want 1 or 2, and 3 is a major pain. You’re not looking to figure out exactly what went wrong; you just want to show the user “Arithmetic error” or something, rather than crashing or giving a wildly incorrect answer.

Therefore, I propose that we add failable arithmetic operators (e.g. +?). These return nil on overflow and the result on non-overflow.

  1 +? 1 // => Optional(1)
  Int.max +? 1 // => nil

  1 -? 1 // => Optional(0)
  Int.min -? 1 // => nil

  1 /? 1 // => Optional(1)
  1 /? 0 // => nil

One important consideration is that you ought to be able to chain such operations together into expressions like "m *? x +? c”. The simplest way to do this would be to make these operators take optional arguments; then the implementations would all look something like this:

  func +? <Integer: IntegerArithmeticType>(lhs: Integer?, rhs: Integer?) -> Integer? {
      guard let lhs = lhs, rhs = rhs else {
          return nil
      }
    
      let (result, overflowed) = Integer.addWithOverflow(lhs, rhs)
      if overflowed {
          return nil
      }
      else {
          return result
      }
  }

However, that might encourage people to misuse these operators to simply perform arithmetic on optional integers even when they don’t want nil-on-overflow. There may be some clever way to get these operators to allow chaining from other failable operators, but prevent the use of other nil-returning expressions; I’m not sure how that would be done, but I wouldn’t mind if Swift forced that sort of hygiene on this feature.

An alternative approach might be to write throwing variants of these operators which require the use of “try". These would have a few advantages: they would naturally short-circuit, they wouldn’t form an attractive nuisance for people trying to do arithmetic with optional integers, and the errors they throw could provide additional detail. However, unlike the association of Optional with ?, there’s no obvious way to associate these operators with the idea of trying and throwing. For this example, I’ve used “+!” purely for lack of a better idea:

  do {
      print(try 1 +! 2 +! Int.max)
  }
  catch let error as IntegerArithmeticError { // or perhaps IntegerArithmeticError<Int>
      print(error) // AdditionOverflow, or perhaps even AdditionOverflow(3, 9223372036854775807)
  }

Of course, if you really did just want an optional, you could always use “try?” with this.

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #2

Currently, Swift has three ways to handle potential overflows and other errors in arithmetic:

  // 1: Crashes
  Int.max + 1

  // 2: Returns the wrong answer (Int.min in this case)
  Int.max &+ 1

  // 3: Returns a tuple with the value of &+ and a boolean indicating whether it overflowed
  Int.addWithOverflow(Int.max, 1)

The problem is, if you want to handle overflows in some simple way, none of these are very good. 1 terminates your app, 2 gives the wrong answer, and 3 is very awkward to use. If you’re, for instance, working with numbers input by the user or downloaded from the Internet, you don’t want 1 or 2, and 3 is a major pain. You’re not looking to figure out exactly what went wrong; you just want to show the user “Arithmetic error” or something, rather than crashing or giving a wildly incorrect answer.

Therefore, I propose that we add failable arithmetic operators (e.g. +?). These return nil on overflow and the result on non-overflow.

  1 +? 1 // => Optional(1)
  Int.max +? 1 // => nil

  1 -? 1 // => Optional(0)
  Int.min -? 1 // => nil

  1 /? 1 // => Optional(1)
  1 /? 0 // => nil

One important consideration is that you ought to be able to chain such operations together into expressions like "m *? x +? c”. The simplest way to do this would be to make these operators take optional arguments; then the implementations would all look something like this:

This is similar to the request for a “force unwrap” operator that is catch’able. If we were to do something along these lines, I’d rather see these be modeled as operators that can throw, rather than operators that produce an optional. This will allow them to chain properly and compose correctly with other operations.

There are other questions in this family: we’ve discussed adding saturating integer arithmetic operators as a way to handle this. We have also discussed the idea of having “-ffast-math” floating point operators as well. If you take this to the logical conclusion, you end up with large families of operators, all distinguished by magic sigil families that no one can remember :-)

Another way to think about this as a need to modify the behavior of failing operators. This is not intended as a syntax proposal (just to get the idea across), but wouldn’t it be cool to be able to do:

  {
      #pragma failure_should_throw // again, #pragma is really not the right way to spell this :-)
      try t = m * x + c
  }

and have the operators magically do the right thing? Then you could generalize the behavior to support other families by using an english word to describe the semantics.

-Chris

···

On Dec 4, 2015, at 2:40 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

  func +? <Integer: IntegerArithmeticType>(lhs: Integer?, rhs: Integer?) -> Integer? {
      guard let lhs = lhs, rhs = rhs else {
          return nil
      }

      let (result, overflowed) = Integer.addWithOverflow(lhs, rhs)
      if overflowed {
          return nil
      }
      else {
          return result
      }
  }

However, that might encourage people to misuse these operators to simply perform arithmetic on optional integers even when they don’t want nil-on-overflow. There may be some clever way to get these operators to allow chaining from other failable operators, but prevent the use of other nil-returning expressions; I’m not sure how that would be done, but I wouldn’t mind if Swift forced that sort of hygiene on this feature.

An alternative approach might be to write throwing variants of these operators which require the use of “try". These would have a few advantages: they would naturally short-circuit, they wouldn’t form an attractive nuisance for people trying to do arithmetic with optional integers, and the errors they throw could provide additional detail. However, unlike the association of Optional with ?, there’s no obvious way to associate these operators with the idea of trying and throwing. For this example, I’ve used “+!” purely for lack of a better idea:

  do {
      print(try 1 +! 2 +! Int.max)
  }
  catch let error as IntegerArithmeticError { // or perhaps IntegerArithmeticError<Int>
      print(error) // AdditionOverflow, or perhaps even AdditionOverflow(3, 9223372036854775807)
  }

Of course, if you really did just want an optional, you could always use “try?” with this.

--
Brent Royal-Gordon
Architechies

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


(Joe Groff) #3

This is a place where scoped `import` might help too. For instance, you could import a module with throwing variants of the operators:

{
  import ThrowingOnOverflow
  try t = m * x + c
}

-Joe

···

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

On Dec 4, 2015, at 2:40 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

Currently, Swift has three ways to handle potential overflows and other errors in arithmetic:

  // 1: Crashes
  Int.max + 1

  // 2: Returns the wrong answer (Int.min in this case)
  Int.max &+ 1

  // 3: Returns a tuple with the value of &+ and a boolean indicating whether it overflowed
  Int.addWithOverflow(Int.max, 1)

The problem is, if you want to handle overflows in some simple way, none of these are very good. 1 terminates your app, 2 gives the wrong answer, and 3 is very awkward to use. If you’re, for instance, working with numbers input by the user or downloaded from the Internet, you don’t want 1 or 2, and 3 is a major pain. You’re not looking to figure out exactly what went wrong; you just want to show the user “Arithmetic error” or something, rather than crashing or giving a wildly incorrect answer.

Therefore, I propose that we add failable arithmetic operators (e.g. +?). These return nil on overflow and the result on non-overflow.

  1 +? 1 // => Optional(1)
  Int.max +? 1 // => nil

  1 -? 1 // => Optional(0)
  Int.min -? 1 // => nil

  1 /? 1 // => Optional(1)
  1 /? 0 // => nil

One important consideration is that you ought to be able to chain such operations together into expressions like "m *? x +? c”. The simplest way to do this would be to make these operators take optional arguments; then the implementations would all look something like this:

This is similar to the request for a “force unwrap” operator that is catch’able. If we were to do something along these lines, I’d rather see these be modeled as operators that can throw, rather than operators that produce an optional. This will allow them to chain properly and compose correctly with other operations.

There are other questions in this family: we’ve discussed adding saturating integer arithmetic operators as a way to handle this. We have also discussed the idea of having “-ffast-math” floating point operators as well. If you take this to the logical conclusion, you end up with large families of operators, all distinguished by magic sigil families that no one can remember :slight_smile:

Another way to think about this as a need to modify the behavior of failing operators. This is not intended as a syntax proposal (just to get the idea across), but wouldn’t it be cool to be able to do:

{
     #pragma failure_should_throw // again, #pragma is really not the right way to spell this :slight_smile:
     try t = m * x + c
}

and have the operators magically do the right thing? Then you could generalize the behavior to support other families by using an english word to describe the semantics.


(Brent Royal-Gordon) #4

Another way to think about this as a need to modify the behavior of failing operators. This is not intended as a syntax proposal (just to get the idea across), but wouldn’t it be cool to be able to do:

{
     #pragma failure_should_throw // again, #pragma is really not the right way to spell this :slight_smile:
     try t = m * x + c
}

and have the operators magically do the right thing? Then you could generalize the behavior to support other families by using an english word to describe the semantics.

I like the idea of having multiple semantics and selecting the one you want for an entire expression, rather than dangling sigils on all your operators.

This is, of course, easy to do if you introduce enough abstraction: <https://gist.github.com/brentdax/6fc5d717f7c85e159ec7> I assume this would be very slow as written, but perhaps the optimizer could do something along the lines of constant folding to convert the operation tree into straight-line code.

···

--
Brent Royal-Gordon
Architechies