Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cast-Free-Arithmetic in Swift v2 normal

Rich Fox
September 25, 2015

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
Tweet

More Decks by Rich Fox

Other Decks in Programming

Transcript

  1. 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 :
  2. 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
  3. Protocol Extensions • One protocol for all compatible number types.


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

  4. 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)
  5. • Common initializers to define 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 {}
  6. 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 } }
  7. Pattern Matching • Can be used by protocol to figure

    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..") }
  8. 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) }
  9. 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()
  10. Case 1: • example test case:
 • let x: Int

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

    NumberConvertible
 • Use convert() on lhs and rhs to same type
 • convert() to infered generic result
  12. 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() }
  13. 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() }
  14. • 
 
 
 (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?
  15. 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 }
  16. • Duplicate operator definition, 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() }
  17. Ambiguous use of operator Only works with five or less

    operands • let z: Double = w + x + y
  18. 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
  19. 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()
  20. Expression Too Complex Seems to happens more easily when compiler

    has harder time choosing an operator to use 
 Many definitions 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
  21. What to do about it? • Rage tweet @clattner_llvm ?

    #ExpressionTooComplex
 
 
 
 
 
 
 • Maybe file a radar if it seems not complex…
  22. 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