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:
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:
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:
+
operator+=
operatorWe 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.
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:
3.129.249.194