Cast-Free Arithmetic in Swift

Casting numbers of different types when the result type could easily be inferred can be a pain. In this talk, Rich Fox trades a small amount of type-safety for a great amount of convenience. Using Swift 2.0’s protocol extensions, pattern matching, generics, and operator overloading, Rich simplifies number arithmetic between types.


Hi, I’m Richard Fox. I write in my blog, and am an iOS developer at Propeller Labs, a dev. shop that specializes in MVPs and mobile (if interested, we are currently looking for Swift enthusiasts to join us). My talk today is about exploring Cast-Free Arithmetic in Swift.

Arithmetic Comparison (01:06)

In Objective-C, we have syntax like this:

float   w = 5;
double  x = 10;
int     y = 15;

CGFloat z = w + x + y;

In Swift, this is the equivalent:

let w:Float  = 5
let x:Double = 10
let y:Int    = 15
let z:CGFloat = CGFloat(w) + CGFloat(x) + CGFloat(y)

Strong typing is great, sometimes it is scary. It should be more concise.

Pre-Swift 2.0 (01:31)

Even before Swift 2.0, I tried to solve this problem for my personal use by a simple extension in number type, and adding getters. This works OK, except if you have five getters for each number type, you are repeating code:

extension Double {
    var c:CGFloat {
        return CGFloat(self)
    }
    //. . .
}

extension Int {
    var c:CGFloat {
        return CGFloat(self)
    }
    //. . .
}

extension Float {
    var c:CGFloat {
        return CGFloat(self)
    }
    //. . .
}

//let y:CGFloat = 1 + x.c

With Swift 2.0’s protocol extensions inspired me to make one protocol for all compatible number types; I could use simple dot syntax to convert each type, powered by pattern matching.

How Number Casting Works (02:54)

Each number type is a struct. In the definition of each of the number types, there is the initializer for each type that can be cast from. In the standard library definition you will see these intializers.

init(_ v: Float ), init(_ v: Double)
let x: Float = 5.0
let y = Float(x) OR let y = Float.init(x)

You can even do float.init and cast it. If you Command-click on any part of the code, you can see exactly where it is in the standard library.

To make this definition work for us, we define our protocol with the initializers that are required to do casting. We can extend our number types to the number of convertible protocol. Since all of the number types already implement all of these inits, it should work without us adding anything, except in the case of CGFloat.

CGFloat is a little different from the rest: it is not defined in the standard library — it’s part of Core Graphics. Unlike numbers in the standard library, it doesn’t have init conversions. Luckily it is easy to create one by simply creating an extension. We can extend it and self = value its own type.

protocol NumberConvertible {
    init (_ value: Int) 
    init (_ value: Float) 
    init (_ value: Double) 
    init (_ value: CGFloat)
}

extension CGFloat : NumberConvertible {}
extension Double  : NumberConvertible {}
extension Float   : NumberConvertible {}
extension Int     : NumberConvertible {}

extension CGFloat{
    public  init(_ value: CGFloat){
        self = value 
    }
}

Pattern Matching (05:02)

switch self {
    case let x as CGFloat:
        print("x is a CGFloat")
    case let x as Float:
        print("x is a CGFloat")
    case let x as Int:
        print("x is a CGFloat")
    case let x as Double:
        print("x is a CGFloat")

    default:
        print("x is unknown..")
}

We use this pattern matching in our protocol to figure out what type self is. We need to know what type self is so that we can plug it into one of those intializers that we defined in the protocol. Casting to a type like so sometimes leaks memory but that is now fixed in Swift 2.1.

Creating the Extension (05:56)

extension NumberConvertible {

    private func convert<T: NumberConvertible>() -> T {
        switch self {
            case let x as CGFloat:
                return T(x) //T.init(x)
            case let x as Float:
                return T(x)
            case let x as Double:
                return T(x)
            case let x as Int:
                return T(x)
            default:
                assert(false, "NumberConvertible convert cast failed!")
                return T(0)
        }
    }
    public var c:CGFloat{
        return convert()
    }
    //...
}

Inside the extension, we will define the private function convert(). Convert will return a generic type. We plug in the piece that finds out what type self is, and go through all of the compatible number types that have intializers. Once we determine which kind it is, we can call that initializer T.init(x) since T conforms to NumberConvertible. Now we can cast using our dot property getter syntax here. We can just add it in one place, in the extension here, along with all the other dot property getters.

Converting Without Casting (07:29)

We can now cast without declaring a type. If convert is not private we can take two number types, do .convert(), and set it equal to Y, and it will figure out what type it is, without us telling what it should cast to.

let w: Double = 4.4
let x: Int = 5
let y: Float = w.convert() + x.convert()

But we can make it easier. This below is probably the simplest case. We have two different types that returned a third different type.

let x: Int = 5
let y: CGFloat = 10
let z: Double = x + y

We use operator overloading. We will overload our operator using three generics that conform to NumberConvertible. We will take the lhs and the rhs of that operator, and use the convert() on both of them so they are the same type. We will use the standard library definition (because they are the same type we can do that now), and solve the operation. We will use convert() one more time to cast back to the generic return type. This is the solution:

func + <T:NumberConvertible, U:NumberConvertible, V:NumberConvertible>(lhs: T, rhs: U) -> V {
    let v: Double = lhs.convert()
    let w: Double = rhs.convert()
    return (v + w).convert()
}

We have an operator for addition, and that solves the first case. Let’s now add a second operator and a third number. We have four types in total. However, it does not work as expected below.

func + <T:NumberConvertible, U:NumberConvertible, V:NumberConvertible>(lhs: T, rhs: U) -> V {
    let v: Double = lhs.convert()
    let w: Double = rhs.convert()
    return (v + w).convert()
}

Which Operator Definition is the Compiler Using? (09:44)

The first operator seems to be using our custom operator, and the second is using the standard library definition of the operator. Since the return type of our custom operator is a generic type, the first operator has to have something to infer the third number’s type. Since the third number type is a float, when we do the second operation, we have a float and a float. For some reason the compiler is not smart enough to look at the return type, which is not a float…

Handling the Compiler (10:51)

The first thing I tried was compromising with the compiler.

public typealias PreferredType = Double

public func + <T:NumberConvertible, U:NumberConvertible>(lhs: T, rhs: U) -> PreferredType
{
    let v: PreferredType = lhs.convert()
    let w: PreferredType = rhs.convert()
    return v+w
}

A single return type works, but it only returns a double. The compiler is happy, but that is not a great solution: you can only return one type.

We can give the compiler more options. We will take our definition, and duplicate it (try only using two generics where the lhs and the rhs are both the same type). Of course, we still have our inferred return type (our original definition).

Using both of these operators together, our scheme almost works. However, at some point the compiler is confused and does not know which one to use.

Now we remove that extra overload that we created. Adding a zero in there causes the compiler to stop complaining. It seemed to be an issue with having two operations together.

Optimizing the Operators (13:39)

extension NumberConvertible {
    private typealias CombineType = (Double,Double) -> Double
    private func operate<T:NumberConvertible,V:NumberConvertible>(b:T, @noescape combine:CombineType) -> V{
        let x:Double = self.convert()
        let y:Double =    b.convert()
        return combine(x,y).convert()
    }
}

public func + <T:NumberConvertible, U:NumberConvertible,V:NumberConvertible>(lhs: T, rhs: U) -> V {
    return lhs.operate(rhs, combine: + )
}
public func - <T:NumberConvertible, U:NumberConvertible,V:NumberConvertible>(lhs: T, rhs: U) -> V {
    return lhs.operate(rhs, combine: - )
}

We can make it even nicer by doing even more extensions on protocols. I went back to number convertible. I threw in an extension for a function that takes in two doubles and returns a double. Then, I define this operate function that takes in a generic type. I also added a combine type alias CombineType function, and returns another generic.

Inside the implementation, I convert self and the generic input into doubles, and then we can plug both of those into our combine function. With the result, we can convert it to the return type. Then, we can replace our previous implementation of the operator with one line: lhs.operate, rhs and a single, just one operator for the function. That is really nice to look at and easy to use in all of the other arithmetic operators.

“Expression Too Complex” (15:21)

The expression was too complex to be solved in a reasonable time. It suggests that we break up the expression in two distinct sub-expressions, or make multiple expressions (even more when I was using overloaded operators).

If you do get this error, maybe you can rage tweet Chis Lattner #ExpressionTooComplex. That might help if you find a case where you think it actually does not really seem that complex. You can file radar, but there is not much else you can do.

Conclusion (16:47)

In conclusion, we can get castless arithmetic working with some constraints. We have mild inferrence confusion that we can just add plus zero to fix. We do also have the potential for complex expression errors. Our original implimentation, which was .property conversions, is not too bad. I use that method myself, even in production, as it doesn’t take too much away from type safety.

All of the code for cast-free arithmetic is available here.

Q&A (17:51)

Q: Swift has the ampersand operator to indicate performing arithmetic explicitly with the potential for overflow. Since with this approach we potentially throw data away, should we use another operator to be explicit about it?

Rich: I like that idea. I mostly did this for fun and exploration, but for readability purposes this could be a viable decision.


Richard Fox

Richard Fox

Rich is an iOS Developer at Propeller Labs where he has been developing in Swift since the beginning of 2015. In January 2014, he moved to the Bay Area from Hangzhou, China, where he lived for four years while completing a master's program in teaching Mandarin as a second language, studied the game of Go, and worked at Viva Video on an iOS video editing app. Currently obsessed with Swift 2.0, he tweets about it @RGfox, and blogs about it at foxinswift.com.