Extending base types to conform to custom protocols

Now, we want to be able to use any of the integer types as types in our MutableVector3D<T> and ImmutableVector3D<T> classes. We want to make the two classes capable of working with elements of any integer type supported in Swift, that is, any of the following types, in addition to the floating point types that the classes already support:

  • Int
  • Int16
  • Int32
  • Int64
  • Int8
  • UInt
  • UInt16
  • UInt32
  • UInt64
  • UInt8

It seems to be a pretty simple task. We would just have to replace the generic type constraint in each class declaration from FloatingPoint to a more generic protocol. We need a protocol to which all the previously enumerated types conform to, and to which the floating point types also conform. However, we will face a big problem: we don't have a protocol that will allow us to easily build the generic type constraint and make the two classes work. Let's analyze the problem first and then we will build a solution.

All the types we need to support conform to SignedNumber; therefore, our first approach might be to replace FloatingPoint with SignedNumber in the generic type constraint. This solution won't work in either the MutableVector3D<T> or the ImmutableVector3D<T> class. However, it is important to understand why it doesn't work. The following lines show the line that declares the MutableVector3D<T> class with the edit; the body of the class remains without changes. The code file for the sample is included in the swift_3_oop_chapter_06_14 folder:

    open class MutableVector3D<T: SignedNumber> { 

After we enter the previous code in the Playground, it will generate the following errors:

error: binary operator '+=' cannot be applied to two 'T' operands
        x += deltaX
        ~ ^  ~~~~~~
error: binary operator '+=' cannot be applied to two 'T' operands
        y += deltaY
        ~ ^  ~~~~~~
error: binary operator '+=' cannot be applied to two 'T' operands
        z += deltaZ
        ~ ^  ~~~~~~

The following screenshot shows the Playground with the generated errors:

Extending base types to conform to custom protocols

The generated errors make it easy to understand the problem. The SignedNumber protocol doesn't require the += operator, so we cannot apply the += operator to the T operands that just conform to this protocol.

Now, let's try to generate the ImmutableVector3D<T> class and check whether it works with a similar approach. The following lines show the line that declares the ImmutableVector3D<T> class with the edit; the body of the class remains without changes. The code file for the sample is included in the swift_3_oop_chapter_06_15 folder:

    open class ImmutableVector3D<T: SignedNumber> { 

After we enter the previous code in the Playground, it will generate the following error:

error: binary operator '+' cannot be applied to two 'T' operands
        return ImmutableVector3D(x: x + deltaX, y: y + deltaY, z: z + deltaZ)
                                    ~ ^ ~~~~~~

The following screenshot shows the Playground with the generated error:

Extending base types to conform to custom protocols

As in the previous case, the generated error makes it easy to understand the problem. The SignedNumber protocol doesn't require the + operator, so we cannot apply the + operator to the T operands that just conform to this protocol.

Basically, we need all the integer and floating point types to do the following:

  • Provide an initializer that creates an instance initialized to zero
  • Implement the + operator
  • Implement the += operator

We just need to create a protocol that specifies these requirements and extends all the integer and floating point types we want to be used as types in our MutableVector3D<T> and ImmutableVector3D<T> classes. We must extend these types to conform to the new protocol.

The following lines show the code that declares the new NumericForVector protocol. We must add these lines before the declaration of the existing classes. The code file for the sample is included in the swift_3_oop_chapter_06_16 folder:

    public protocol NumericForVector { 
      init() 
 
      static func +(lhs: Self, rhs: Self) -> Self 
      static func +=(lhs: inout Self, rhs: Self) 
    } 

The protocol declares an initializer without arguments. All the numeric types provide an initializer without arguments to generate a value of the type initialized to zero. It is exactly what we need to initialize our Immutable3DVector to an origin vector.

Then, the protocol declares the + static function that represents the + operator. The function requires two arguments, lhs and rhs, which are acronyms for left-hand side and right-hand side, to specify the values on the left-hand side and right-hand side of the operator. Both arguments are of the Self type.

Tip

In protocols, Self means the actual type that implements the protocol, and it is different from self with a lowercase s that we use in methods and that refers to the actual instance. The + static function returns Self, so the implementation of this function in Double receives two Double arguments and returns a Double argument with the result of the sum of the two received values. The implementation of this function in Int receives two Int arguments and returns an Int argument with the result of the sum of the the two received values.

Finally, the protocol declares the += function that represents the += operator. The function requires two arguments: lhs and rhs. In this case, the first argument is an in/out parameter as it includes the inout keyword at the start of the parameter definition. Thus, Swift passes the value of lhs, and the function can modify it and pass it back out of the function to replace the original value. Both arguments are of the Self type and the += function returns Self. Now, we have to extend all the floating point and integer types we want to be used as types in our MutableVector3D<T> and ImmutableVector3D<T> classes to make it conform to the recently created NumericForVector protocol, as follows. We must add these lines after the declaration of the NumericForVector protocol and before the declaration of the classes. The code file for the sample is included in the swift_3_oop_chapter_06_16 folder:

    // Floating point 
    extension Double: NumericForVector { } 
    extension Float: NumericForVector { } 
    extension Float80: NumericForVector { } 
    // Signed integers 
    extension Int: NumericForVector { } 
    extension UInt: NumericForVector { } 
    extension Int16: NumericForVector { } 
    extension Int32: NumericForVector { } 
    extension Int64: NumericForVector { } 
    extension Int8: NumericForVector { } 
 
    // Unsigned integers 
    extension UInt16: NumericForVector { } 
    extension UInt32: NumericForVector { } 
    extension UInt64: NumericForVector { } 
    extension UInt8: NumericForVector { } 

We don't need to add code to make any of the numeric types conform to the new NumericForVector protocol because the types already implement the necessary actions to conform to the protocol. We just need to have a protocol that groups all the requirements to use it as a type constraint for the generic type in our two classes.

Now, we have to replace SignedNumber with NumericForVector in the generic type constraint for the MutableVector3D<T> and ImmutableVector3D<T> classes. The following lines show the line that declares the MutableVector3D<T> class with the edit; the body of the class remains without changes. The code file for the sample is included in the swift_3_oop_chapter_06_16 folder:

    open class MutableVector3D<T: NumericForVector> { 

The class name is followed by a less than sign (<), a T that identifies the generic type parameter, a colon (:), and a protocol name that the T generic type parameter must conform to, that is, the NumericForVector protocol. The protocol specifies the requirement for a += function; therefore, the sum method can apply this operator to the stored properties (x, y, and z) and delta arguments (deltaX, deltaY, and deltaZ), all of them of the T type. The following lines show the code for the new ImmutableVector3D<T> class that works as expected. The edited lines are highlighted. The code file for the sample is included in the swift_3_oop_chapter_06_16 folder:

    open class ImmutableVector3D<T: NumericForVector> { 
      open let x: T 
      open let y: T 
      open let z: T 
     
      init(x: T, y: T, z: T) { 
        self.x = x 
        self.y = y 
        self.z = z 
      } 
     
      open func summed(deltaX: T, deltaY: T, deltaZ: T) -> 
      ImmutableVector3D<T> { 
        return ImmutableVector3D<T>(x: x + deltaX, y: y + deltaY, z: 
        z + deltaZ) 
      } 
     
      open func printValues() { 
        print("X: (self.x), Y: (self.y), Z: (self.z)") 
      } 
      
      open class func makeEqualElements(initialValue: T) -> 
      ImmutableVector3D<T> { 
        return ImmutableVector3D<T>(x: initialValue, y: initialValue, 
        z: initialValue) 
      } 
     
      open class func makeOrigin() -> ImmutableVector3D<T> { 
        let zero = T() 
        return makeEqualElements(initialValue: zero) 
      } 
    } 

The class name is followed by a less than sign (<), a T that identifies the generic type parameter, a colon (:), and a protocol name that the T generic type parameter must conform to, that is, the NumericForVector protocol. The protocol specifies the requirement for a + function, so the summed method can apply this operator to the stored properties (x, y, and z) and delta arguments (deltaX, deltaY, and deltaZ) to use the results as arguments to create a new instance of ImmutableVector3D<T>.

The originVector type method calls the initializer without arguments to create a value of the T type initialized to zero. We can use this initializer because we specified it as a requirement in the NumericForVector protocol. Now, we can create instances of any of the following:

  • MutableVector3D<Double>
  • MutableVector3D<Float>
  • MutableVector3D<Float80>
  • MutableVector3D<Int>
  • MutableVector3D<Int16>
  • MutableVector3D<Int32>
  • MutableVector3D<Int64>
  • MutableVector3D<Int8>
  • MutableVector3D<UInt>
  • MutableVector3D<UInt16>
  • MutableVector3D<UInt32>
  • MutableVector3D<UInt64>
  • MutableVector3D<UInt8>
  • ImmutableVector3D<Double>
  • ImmutableVector3D<Float>
  • ImmutableVector3D<Float80>
  • ImmutableVector3D<Int>
  • ImmutableVector3D<Int16>
  • ImmutableVector3D<Int32>
  • ImmutableVector3D<Int64>
  • ImmutableVector3D<Int8>
  • ImmutableVector3D<UInt>
  • ImmutableVector3D<UInt16>
  • ImmutableVector3D<UInt32>
  • ImmutableVector3D<UInt64>
  • ImmutableVector3D<UInt8>

The following lines create instances of MutableVector3D<T> and ImmutableVector3D<T> with the generic type parameter set to Int and UInt. The code also calls the mutating sum or the nonmutating summed method for each instance. Then, the code calls the printValues method. The code file for the sample is included in the swift_3_oop_chapter_06_16 folder:

    let mutableVector4 = MutableVector3D<Int>(x: -10, y: -11, z: -12) 
    mutableVector4.sum(deltaX: 7, deltaY: 8, deltaZ: 9) 
    mutableVector4.printValues() 
     
    let mutableVector5 = MutableVector3D<UInt>(x: 10, y: 11, z: 12) 
    mutableVector5.sum(deltaX: 7, deltaY: 8, deltaZ: 9) 
    mutableVector5.printValues() 
  
    let immutableVector6 = ImmutableVector3D<Int>(x: -7, y: -2, z: -1) 
    let immutableVector7 = immutableVector6.summed(deltaX: 3, 
    deltaY: 12, deltaZ: 14) 
    immutableVector7.printValues() 
 
    let immutableVector8 = ImmutableVector3D<UInt>(x: 7, y: 2, z: 1) 
    let immutableVector9 = immutableVector8.summed(deltaX: 3, 
    deltaY: 12, deltaZ: 14) 
    immutableVector9.printValues() 

The following lines show the output generated by the preceding code:

X: -3, Y: -3, Z: -3
X: 17, Y: 19, Z: 21
X: -4, Y: 10, Z: 13
X: 10, Y: 14, Z: 15

The following screenshot shows the Playground with the types generated in each line specified on the right-hand side:

Extending base types to conform to custom protocols

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.249.194