20k

# Cast-Free-Arithmetic in Swift v2 normal

Have you ever been annoyed by all the casting required to do simple arithmetic between mixed number types in Swift? Many of you who came into Swift after years of Objective-C, myself included, said things like, "Swift is great, but doing simple arithmetic can be quite annoying! So much casting!" On the one hand it brings a higher level of type safety to number types, but on the other it can be an inconvenient amount of extra syntax, especially for simple operations that could otherwise be quite concise. This talk seeks to ease this inconvenience by exploring how best to handle mixed number type operations while contemplating how much type-safety is ok to let go of in the process. Utilizing all the goodies of Swift 2.0 along the way, the talk will also cover protocol extensions, pattern matching, generics, inference, and operator overloading.

## Rich Fox

September 25, 2015

## Transcript

4. ### Arithmetic Comparison float w = 5; double x = 10;

int y = 15; CGFloat z = w + x + y; let w:Float = 5 let x:Double = 10 let y:Int = 15 let z:CGFloat = CGFloat(w) + CGFloat(x) + CGFloat(y) Objective-C : Swift :
5. ### Pre-Swift 2.0 • dot syntax for quick conversions.  • exhaustive

implementation, so much repeated 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
6. ### Protocol Extensions • One protocol for all compatible number types.

• Extend to use a dot syntax for casting  • We also need some pattern matching…

8. ### How Number Casting Works • has initializer for each other

compatible type  • i.e. init(_ v: Float ), init(_ v: Double) • let x: Float = 5.0 • let y = Float(x) OR let y = Float.init(x)
9. ### • Common initializers to deﬁne our protocol

• Looks like something is missing! protocol NumberConvertible {  init (_ value: Int) init (_ value: Float) init (_ value: Double) init (_ value: CGFloat) }    z Back to Task extension CGFloat : NumberConvertible {} extension Double : NumberConvertible {} extension Float : NumberConvertible {} extension Int : NumberConvertible {}
10. ### z • CGFloat has no initializer for CGFloat.   •

Luckily it is trivial to extend one into it. Type 'CGFloat' does not conform to protocol 'NumberConvertible' extension CGFloat{ public init(_ value: CGFloat){ self = value } }
11. ### Pattern Matching • Can be used by protocol to ﬁgure

out it’s type 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..") }
12. ### Putting it all together extension NumberConvertible { } private func

convert<T: NumberConvertible>() -> T { } public var c:CGFloat{ return convert() } //... 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) }
13. ### Castless Conversion • We can cast without declaring a type

• Let’s make it easier with operator overloading • let w: Double = 4.4  let x: Int = 5  let y: Float = w.convert() + x.convert()
14. ### Case 1: • example test case:  • let x: Int

= 5  let y: CGFloat = 10  let z: Double = x + y
15. ### Case 1: • Operator overloading  • Three generics conforming to

NumberConvertible  • Use convert() on lhs and rhs to same type  • convert() to infered generic result
16. ### Case 1: 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() }
17. ### Case 2: • example test case:    • why doesn’t

it work? • let x: Int = 5  let y: CGFloat = 10  let z: Float = 15 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() }
18. ### •       (x + y) + z

(Int + CGFloat) + Float    Float + Float    • Double = Float + Float //Can’t return Double    (uses STDLib version of +) + using custom operator  + using STDLib definition of operator Which Operator Definition is the Compiler Using?
19. ### Compromising with the Compiler • A Single return type?

• Compromises being able to return multiple types. 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 }
20. ### • Duplicate operator deﬁnition, except now…  lhs and rhs shall

be the same type Giving the Compiler More Options /// `lhs` and `rhs` are the same type! func + <T:NumberConvertible, U:NumberConvertible>(lhs: T, rhs: T) -> U { let v: Double = lhs.convert() let w: Double = rhs.convert() return (v + w).convert() } /// The original function `lhs` and `rhs` possibly different 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() }
21. ### Ambiguous use of operator Only works with five or less

operands • let z: Double = w + x + y
22. ### func + <T:NumberConvertible, U:NumberConvertible>(lhs: T, rhs: T) -> U {

let v: Double = lhs.convert() let w: Double = rhs.convert() return (v + w).convert() } 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() } • Backtrack, remove the extra overload function          • And let’s just give the compiler what it wants… A Simpler Solution
23. ### Optimized Operators extension NumberConvertible { } 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: - ) } /// ... other operators ... 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()
24. ### Expression Too Complex Seems to happens more easily when compiler

has harder time choosing an operator to use   Many deﬁnitions for a single arithmetic operator?   Compiler has too much to do? • Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions
25. ### What to do about it? • Rage tweet @clattner_llvm ?

#ExpressionTooComplex              • Maybe ﬁle a radar if it seems not complex…
26. ### Overall Conclusion • We can get castless arithmetic working with

constraints • mild inference confusion • potential for complex expression error  • Dot property conversions - let z:Int = x.i + y.i • add convenience • doesn't take away type safety