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

Cast-Free-Arithmetic in Swift v1 with Builds

Rich Fox
September 25, 2015

Cast-Free-Arithmetic in Swift v1 with Builds

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; Objective-C :
  2. Arithmetic Comparison float w = 5; double x = 10;

    int y = 15; CGFloat z = w + x + y; Objective-C : Swift :
  3. 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 :
  4. Pre-Swift 2.0 • dot syntax for quick conversions.
 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
  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

  7. 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)
  9. How Number Casting Works • has initializer for each other

    compatible type
 • i.e. init(_ v: Float ), init(_ v: Double) • let x: Float = 5.0
  10. 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)
  11. • Common initializers to define our protocol
 
 
 


    
 protocol NumberConvertible {
 init (_ value: Int) init (_ value: Float) init (_ value: Double) init (_ value: CGFloat) }
 
 Back to Task
  12. • Common initializers to define our protocol
 
 
 


    
 protocol NumberConvertible {
 init (_ value: Int) init (_ value: Float) init (_ value: Double) init (_ value: CGFloat) }
 
 Back to Task extension CGFloat : NumberConvertible {} extension Double : NumberConvertible {} extension Float : NumberConvertible {} extension Int : NumberConvertible {}
  13. • Common initializers to define our protocol
 
 
 


    
 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 {}
  14. • 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 {}
  15. z • CGFloat has no initializer for CGFloat. 
 Type

    'CGFloat' does not conform to protocol 'NumberConvertible'
  16. z • CGFloat has no initializer for CGFloat. 
 •

    Luckily it is trivial to extend one into it. Type 'CGFloat' does not conform to protocol 'NumberConvertible'
  17. 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 } }
  18. 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..") }
  19. Putting it all together 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) }
  20. 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) }
  21. Castless Conversion • We can cast without declaring a type


    
 
 
 • let w: Double = 4.4
 let x: Int = 5
 let y: Float = w.convert() + x.convert()
  22. 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()
  23. Case 1: • example test case: • let x: Int

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

    NumberConvertible
 • Use convert() on lhs and rhs to same type

  25. Case 1: • Operator overloading
 • Three generics conforming to

    NumberConvertible
 • Use convert() on lhs and rhs to same type
 • convert() to infered generic result
  26. 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() }
  27. Case 2: • example test case:
 
 • let x:

    Int = 5
 let y: CGFloat = 10
 let z: Float = 15
  28. 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() }
  29. + using custom operator
 + using STDLib definition of operator

    Which Operator Definition is the Compiler Using?
  30. • 
 
 
 (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?
  31. • 
 
 
 (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?
  32. Compromising with the Compiler • A Single return type?
 


    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 }
  33. 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 }
  34. • Duplicate operator definition, except now…
 lhs and rhs shall

    be the same type Giving the Compiler More Options
  35. • 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() }
  36. • 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() }
  37. Ambiguous use of operator Only works with five or less

    operands • let z: Double = w + x + y
  38. Ambiguous use of operator Only works with five or less

    operands • let z: Double = w + x + y
  39. Ambiguous use of operator Only works with five or less

    operands • let z: Double = w + x + y
  40. 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
 
 
 
 
 A Simpler Solution
  41. 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
 
 
 
 
 A Simpler Solution
  42. 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
  43. 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
  44. 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
  45. Optimized Operators extension NumberConvertible { } private typealias CombineType =

    (Double,Double) -> Double private func operate<T:NumberConvertible,V:NumberConvertible>(b:T, @noescape combine:CombineType) -> V{ }
  46. Optimized Operators 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()
  47. 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()
  48. Expression Too Complex • Expression was too complex to be

    solved in reasonable time; consider breaking up the expression into distinct sub-expressions
  49. Expression Too Complex Seems to happens more easily when compiler

    has harder time choosing an operator to use • Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions
  50. 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? • Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions
  51. 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
  52. What to do about it? • Rage tweet @clattner_llvm ?

    #ExpressionTooComplex
 
 
 
 
 
 

  53. What to do about it? • Rage tweet @clattner_llvm ?

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

    constraints • mild inference confusion • potential for complex expression error

  55. 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
  56. 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
  57. 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