[swift-evolution] Failable arithmetic
Chris Lattner
clattner at apple.com
Fri Dec 4 18:13:36 CST 2015
> On Dec 4, 2015, at 2:40 PM, Brent Royal-Gordon <brent at 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 :-)
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
>
> 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list