Week 3—SimpleOptionals and Complex Objects

Goals

Preparation

Optionals

The first part of this lab is designed to get you acquainted with the concept of an optional, which is an important (and extremely useful) feature of the Swift language. At the same time you're going to get a bit more practice with object oriented programming by creating a class which emulates what optionals do.

SimpleOptional

The SimpleOptional class we're going to create is supposed to (more or less) do what an optional does. Just keep in mind that optionals are an integrated feature of the Swift language, and so they are not actually classes. But this implementation of an optional-like class should give you a good idea of what optionals are all about.

The functionality that the SimpleOptional class is going to provide is a generic and uniform mechanism of encoding the "no-value" state for variables and object references. The "no-value" state is very handy for error propagation. For instance, recall that in the previous lab, instantiation of a Fraction object with denominator value of 0 would trigger an assert. This would stop the program execution. This might be fine for a lab exercise, but in big and complex program such errors must be handled gracefully. It would not be acceptable for Excel, for instance, to crash every time you entered a letter value into an cell that is formatted as a number, because the equation that depends on that cell cannot handle invalid input. The program must identify such errors and continuing its operation to informing the user about the problem. And so, the proper way to handle bad initialisation of a Fraction would be not to instantiate an object with invalid state and give the programmer means of detecting the failure. This is where SimpleOptional can be of use.

Create a new OS X / Application / Command Line Tool project in Xcode and name it prog3.1. Create a new swift file and name it SimpleOptional.swift. Here's what should go into the new file:

001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
026:
027:
028:
029:
030:
031:
032:
033:
034:
035:
036:
037:
038:
039:
040:
041:
042:
043:
044:
045:
046:
047:
048:
049:
050:
051:
052:
053:
054:
055:
056:
057:
058:
059:
060:
061:
062:
063:
064:
065:
066:
067:
068:
069:
070:
071:
072:
073:
074:
075:
076:
077:
078:
079:
080:
081:
082:
083:
084:
085:
086:
087:
088:
089:
090:
091:
092:
093:
094:
095:
096:
097:
098:
099:
100:
101:
import Foundation


/**
 Wrapper for value (or an object) with a boolean flag
 that indicates whether the value is defined or not
 
 */
class SimpleOptional : CustomStringConvertible {
    
    // STORED PROPERTIES
    
    let value: Any          //Reference to wrapped value
    let hasValue: Bool      //Flag indicating whether value has been
    //set or not
    
    // COMPUTED PROPERTIES
    
    /**
     - returns: Any Reference to the unwrapped value
     */
    var unwrap: Any {
        
        // Cannot unwrap if there is no value
        assert(self.hasValue, "fatal error: cannot unwrap a NIL")
        
        return self.value
    }
    
    /**
     - returns: String String representation of the wrapped value if not nil,
     "nil" otherwise.
     */
    var description: String {
        if self.hasValue {
            return "SimpleOptional(\(self.value))"
        } else {
            return "nil"
        }
    }
    
    // INITIALISERS
    
    /**
     Initialiser for undefined value
     */
    fileprivate init() {
        self.value = "Undefined"
        self.hasValue = false
    }
    
    /**
     Initialiser that wraps a defined value
     
     - parameter value: Value (or object) to wrap
     */
    init(value: Any) {
        self.value = value
        self.hasValue = true
    }
}

/**
 Equality comparison operator between two SimpleOptionals
 
 Two optionals are considered the same if they both
 wrap a value, or are both nil.  The wrapped values
 are not compared.  This is useful for checking whether
 a SimpleOptional has a value or not
 
 - parameter left: The operand to the left of ==
 - parameter right: The operand to the right of ==
 
 - returns: Bool True if both optionals has a value or
 both have no value, false otherwise
 */
func == (left: SimpleOptional, right: SimpleOptional) -> Bool {
    if right.hasValue == left.hasValue {
        return true
    } else {
        return false
    }
}

/**
 Non-equality comparison operator between two SimpleOptionals
 
 This operator inverts the logic of the == operator
 
 - parameter left: The operand to the left of !=
 - parameter right: The operand to the right of !=
 
 - returns: Bool True if one of the optionals has a value and
 the other doesn't, false otherwise
 */
func != (left: SimpleOptional, right: SimpleOptional) -> Bool {
    return !(left == right)
}

// Define NIL as a SimpleOptional that has no value
let NIL: SimpleOptional = SimpleOptional()

Read the comments carefully and try to understand how the class works. The SimpleOptional is basically a wrapper for a value (or a reference to an object) of any type. Its state consists of two properties: a value and a boolean flag indicating whether that value is set. The properties are immutable, meaning the class state can only be set once, through an initialiser, when the object is created. There are two initialisers. The one without arguments creates a SimpleOptional object with no value and the hasValue property set to false. The other initialiser accepts an argument of Any type and sets the hasValue flag to true. That argument correspond to the value that SimpleOptional is wrapping. The Any type is a special type in Swift that matches any other type. This means that the value parameter of the initialiser call can be set to a value or reference of any type. Note that the first initialiser, the one without arguments, is fileprivate—this means it can be only used from within the SimpleOptions.swift file. (Note that Swift 3 introduced fileprivate, and made private more restrictive. Prior to Swift 3, private would have been used here.)

The class has two computed properties. The unwrap property returns the value property, unless the hasValue flag is not set, in which case an assert will be triggered. The second computer property, description, returns a string representation of the value when hasValue is true, and a "nil" string otherwise.

The class block is followed by two functions that define the equality (==) and non-equality (!=) operators between two SimpleOptional objects. Two SimpleOptional objects are deemed equal if they both hold a value, or if they both do not hold a value—the actual values are not examined during the comparison. Note that the function definining the non-equality operator invokes the equality operator, then simply inverts its return value.

Finally, at the bottom, an instance object of the SimpleOptional is created using the (file)private initialiser. This will set the object's internal hasValue flag to false. This instance is assigned to a constant reference called NIL. Note, that since init() is a fileprivate method, there can be no instantiation of a SimpleOptional object with that initialiser outside this file. NIL will be the only instance of SimpleOptional that has no value.

If you're typing all this out, do not forget the colon and CustomStringConvertible keyword that follows the class definition—its purpose will be explained in the next section.

NIL

Write the following program in main.swift:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
import Foundation

//Declare variable x as a SimpleOptional
var x: SimpleOptional;

//Assign NIL optional to x
x = NIL

//Print x
print("x=\(x)")

//Check if x has a value or not
if x == NIL {
    print("x has no value")
} else {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x.unwrap)")
}

When you run this program, you should get the following output:

x=nil
x has no value

Let's go over the program step by step. First, a variable x of a SimpleOptional type is declared. Then, it's assigned a NIL value. This means, that x holds no value. In the following print statement, the state of the SimpleOptional is shown using its description property. The if statement that follows is a check for whether the optional holds a value or not. If it did, that value would be unwrapped and printed, but in this case, the first branch is taken, because x is NIL.

If you paid close attention, you might have noticed that the print("x=\(x)") statement did what print("x=\(x.description)") would do. How did Swift know to use that method? The reason, as you might suspect, is that Swift syntax introduces a shortcut. For objects that implement a String returning description property, the two statements are equivalent. It makes it much easier for the programmer not to have to type out "description" every time. There is one more condition that the class must satisfy in order for this to work—it must conform to the CustomStringConvertible protocol. In other words, the class must promise that it will implement the description method with the expected signature. Note that in SimpleOptional.swift the class definition is followed by a colon and the CustomStringConvertible keyword. This is where the promise is made so that the compiler is happy. Protocols will be discussed in detail when we come to polymorphism. However, we had to introduce the CustomStringConvertible keyword early on, because not having to type out description every time we interpolate, speeds up development a lot.

A value

Assign a value to x in main.swift, and follow with checks of SimpleOptional's state like so:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
import Foundation

//Declare variable x as a SimpleOptional
var x: SimpleOptional;

//Assign NIL optional to x
x = NIL

//Print x
print("x=\(x)")

//Check if x has a value or not
if x == NIL {
    print("x has no value")
} else {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x.unwrap)")
}

//Assign a value of 3 to x
x = SimpleOptional(value: 3)

//Print x
print("x=\(x)")

//Check if x has a value or not
if x != NIL {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x.unwrap)")
} else {
    print("x has no value")
}

When you run this program, you should get the following additional output:

x=SimpleOptional(3)
x has a value of 3

OK, so what's happening now? Instantiating a SimpleOptional object with a different initialiser creates an object that wraps the passed-in value—the hasValue property of the new object is set to true. When x is printed, the wrapped value is shown within the "SimpleOptional()" string, to signify that the printed object is a SimpleOptional wrapping the value shown. This time the check for NIL is done using the not-equal operator (just to show that it works as well). Since x has a value and NIL does not, they are considered not equal, and so the first branch of the if block is taken, where the value is unwrapped from the SimpleOptional object in the print("x has a value of \(x.unwrap)") statement.

What would happen if the NIL SimpleOptional was unwrapped? If you examine the implementation of the unwrap property, you'll see that an assert will be triggered. Given that programmer has the ability to check whether a SimpleOptional is NIL or not, there should be clear separation of program logic for a the case when a variable has a value and the case when it doesn't. The optional that does not have a value should never be unwrapped.

Enter the Fraction

Next, you'll wrap SimpleOptional around another object. Copy the Fraction.swift file from the previous lab into the prog3.1 folder where main.swift and SimpleOptional.swift files are found. In Xcode, in the project navigator window, select the prog3.1 folder under the prog3.1 target. Then, from the main menu select File -> Add files to prog3.1... and select Fraction.swift from where its just been copied. Make sure that the option "Create folder references" is selected, and press "Add". Fraction.swift should now appear in your project navigator, and from now on it will be compiled with the rest of your program. Hence, you can create objects of the Fraction class in main.swift. Before you do that though, modify the Fraction class to indicate that it conforms to the CustomStringConvertible protocol—just change the declaration line "class Fraction {" to "class Fraction : CustomStringConvertible {". Nothing else needs to be done, because the description methods has been already implemented. From now on, you can do string interpolation of Fractions without explicit calls to the description method.

Add the following code to main.swift:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
import Foundation

//Declare variable x as a SimpleOptional
var x: SimpleOptional;

//Assign NIL optional to x
x = NIL

//Print x
print("x=\(x)")

//Check if x has a value or not
if x == NIL {
    print("x has no value")
} else {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x.unwrap)")
}

//Assign a value of 3 to x
x = SimpleOptional(value: 3)

//Print x
print("x=\(x)")

//Check if x has a value or not
if x != NIL {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x.unwrap)")
} else {
    print("x has no value")
}

//Declare vriable z as a SimpleOptional
var z: SimpleOptional

//Assign a Fraction object 1/3 to z
z = SimpleOptional(value: Fraction(num: 1, den: 3))

//Print z
print("z=\(z)")

//Check if z has a value or not
if z != NIL {
    //If z has a value, unwrap that value
    //and print it
    print("z has a value of \(z.unwrap)")
    //If the value of an optional is an object,
    //the optional must be unwrapped in order
    //to access the properties and methods of that object

    //Unwrap z and force type cast it to a Fraction object
    let v = z.unwrap as! Fraction
    //Print the computed decimal property
    print("The decimal property of z's value is \(v.decimal)")
} else {
    print("z has no value")
}

The program creates a new SimpleOptional with a Fraction object as its value. Run the program and your should get the following additional output:

z=SimpleOptional(1/3)
z has a value of 1/3
The decimal property of z's value is 0.333333

Once again, the initialisation of the SimpleOptional is followed with an if block that checks whether the variable holds a value or not. The case where SimpleOptional is found not to be NIL prints the string representation of the unwrapped object, which in this case is the string representation of the Fraction . The next two lines demonstrate that Fraction methods can be only invoked on the unwrapped value. This is because SimpleOptional does not implement the decimal method—it's the Fraction class that does.

Since the unwrap method of SimpleOptional returns a value of Any type, you need to force a type cast to a Fraction type in order for the compiler to allow invocation of its decimal method. Type casting in Swift can be done with the as keyword. Given that, in general, the compiler has no reason to suppose that Any type could be be treated as a Fraction type, it will not allow the typecast. However, given that you know that the value you just wrapped is a Fraction, you can insist and force the typecast using the as! syntax. And so, the line "let v = z.unwrap as! Fraction" can be read as: "Take the result of unwrapping z, force typecast it to a Fraction, and assign it to a constant v". Because of the forced type cast, v becomes a Fraction.

That's all there is to the SimpleOptional class. It can carry a value of any type or be assigned NIL. It can be tested, and if it not NIL, the stored value can be unwrapped for whatever use. Next, you'll see how the same functionality is provided by the built-in optionals.

Proper optionals

In this part of the exercise, you're going to rewrite main.swift from prog3.1 to do the same thing it did before, but using optionals instead of SimpleOptional. In Xcode, create a new Command Line Tool project and call it prog3.2. Add Fraction.swift to it and write the following program in main.swift:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
import Foundation

//Declare variable x as an optional Int
var x: Int?

//Assign nil to x
x = nil

//Print x (The forcing avoids Xcode warning messages: thanks to whoever suggested this.)
print("x=\(x as Int?)")

//Check if x has a value or not
if x == nil {
    print("x has no value")
} else {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x!)")
}

//Assign a value of 3 to x
x = 3 

//Print x
print("x=\(x as Int?)")

//Check if x has a value or not
if x != nil {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x!)")
} else {
    print("x has no value")
}

//Declare vriable z as an optinal Fraction
var z: Fraction?

//Assign a Fraction object 1/3 to z
z = Fraction(num: 1, den: 3)

//Print z
print("z=\(z as Fraction?)") 

//Check if z has a value or not
if z != nil {
    //If z has a value, unwrap that value
    //and print it
    print("z has a value of \(z!)")
    //If the value of an optional is an object,
    //the optional must be unwrapped in order
    //to access the properties and methods of that object
    
    //Print the computed decimal property of the
    //unwrapped z
    print("The decimal property of z's value is \(z!.decimal)")
} else {
    print("z has no value")
}

This program is meant to mirror the one from prog3.1. An optional is defined by following a variable type with a "?". Whereas SimpleOptional didn't care about the type of the value it was wrapping, an optional (once defined) wraps a specific type. This is quite useful, since there is no need for type casting after unwrapping—an Int? optional always unwraps an Int and a Fraction? optional always unwraps a Fraction. It is also important to keep in mind that Int and Int? are different types.

The undefined value for an optional is represented by the nil keyword (just like the undefined value for a SimpleOptional was represented by the NIL object). The nice thing about the fact that optionals are not really classes is that they can be assigned values using the "=" operator, whereas SimpleOptional had to be instantiated through an initialiser. Unwrapping is done with the "!" operator following the variable. And so, x! here corresponds to x.unwrap in the SimpleOptional scenario). Compare the programs from prog3.1 and prog3.2 side by side to make sure you understand how optionals compares to SimpleOptionals.

The built-in optionals have one more extra feature, which cannot be emulated with the SimpleOptional. The test for nil can be done through unwrapping to a constant. The syntax for this is as follows:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:

import Foundation

//Declare variable x as an optional Int
var x: Int?

//Assign nil to x
x = nil

//Print x
print("x=\(x as Int?)")

//Check if x has a value or not
if x == nil {
    print("x has no value")
} else {
    //If x has a value, unwrap that value
    //and print it
    print("x has a value of \(x!)")
}

//Assign a value of 3 to x
x = 3 

//Print x
print("x=\(x as Int?)")

//Check if x has a value or not
if let xunwrapped = x {
    //Checking through a let will automatically
    //unwrap a non-nil optional into a constant,
    //so no need to unwrap
    print("x has a value of \(xunwrapped)")
} else {
    print("x has no value")
}

//Declare vriable z as an optinal Fraction
var z: Fraction?

//Assign a Fraction object 1/3 to z
z = Fraction(num: 1, den: 3)

//Print z
print("z=\(z as Fraction?)") 

//Check if z has a value or not
if let zu = z {
    //Checking through a let will automatically
    //unwrap a non-nil optional into a constant,
    //so no need to unwrap
    print("z has a value of \(zu)")
    print("The decimal property of z's value is \(zu.decimal)")
} else {
    print("z has no value")
}

These changes to main.swift do not change the program logic. If x is nil, the statement "let xunwrapped = x" evaluates to false. If x is not nil, the value of x is unwrapped into xunwrapped and the entire statement evaluates to true. The type of xunwrapped is the same type as the value that the optional was wrapping—in this particular case an Int. The same goes for the statement that checks and automatically unwraps z if it's not nil, except that zu will be a Fraction.

It should be pointed that these examples of using optionals are rather basic. All they demonstrate is the ability to perform a null check. In fact, optionals were designed to free the programmer from the need to perform compounded null checks through optional chaining. Also, the use of the unwrap "!" operator is generally considered a bad practice. Well structured program should use optionals in such a way that there is no need for explicit unwrapping—like in the last two if statements of the main.swift program above.

Complex objects

The second part of this lab does not refer to objects that are complicated, but to objects that represent complex numbers. In this exercise you will implement a class on your own. It has to conform to a pre-defined interface. At the end, there will be a bit of practice with reference assignment and object copying.

Complex numbers

Like it or not, math is the language of computing. For all the high level programming abstractions, such as OOP, the end product of a compiled program is the machine code, which is a series of mathematical operations. This is why mathematics lends itself so well to example programs, and why in this exercise you'll be implementing a class that can do complex number arithmetic. Complex numbers are very important for graphics—computations for rotations in all game engines rely on quaternions, which are an extension of complex numbers. But for this lab you'll stick to bare essentials.

A complex number consists of two separate values: the real and imaginary part. Given a complex number x, where xr is its real part, and xi is its imaginary part, and a complex number y, where yr is its real part, and yi is its imaginary part, the magnitude, addition, subtraction, multiplication and between these numbers is defined as follows:

If you understand how these formulas were derived, great. If not, it doesn't matter—to implement the methods of the Complex class all you need to do is follow what the formulas tell you without worrying too much about their mathematical meaning.

Complex class

Create a new Command Line Tool project in Xcode and name it prog3.3. Create a new file, call it Complex.swift and paste-in the following code:

001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
026:
027:
028:
029:
030:
031:
032:
033:
034:
035:
036:
037:
038:
039:
040:
041:
042:
043:
044:
045:
046:
047:
048:
049:
050:
051:
052:
053:
054:
055:
056:
057:
058:
059:
060:
061:
062:
063:
064:
065:
066:
067:
068:
069:
070:
071:
072:
073:
074:
075:
076:
077:
078:
079:
080:
081:
082:
083:
084:
085:
086:
087:
088:
089:
090:
091:
092:
093:
094:
095:
096:
097:
098:
099:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
import Foundation

/**
Represents a complex number

*/
class Complex : CustomStringConvertible {
    
    // STORED PROPERTIES
    
    var real: Float;   // Real part of the number
    var imag: Float;   // Imaginary part of the number
    
    // COMPUTED PROPERTIES
    
    /**
    - returns: Float Magnitude of the complex number
    */
    var magnitude: Float {
        //## IMPLEMENT ##//
    }
    
    /**
    - returns: String String representation of a complex number
    */
    var description: String {
        //## IMPLEMENT ##//
    }
    
    // INITIALISERS
    
    
    /**
    Designated initialiser
    
    Real and imaginary parts are passed in the arguments of the initialiser.
    
    - parameter real Real part of the numerator
    - parameter imag Imaginary part of the number
    */
    init(real : Float, imag : Float) {
        //## IMPLEMENT ##//
    }
    
    /**
    Default initialiser
    
    Sets complex number to 0
    */
    convenience init() {
        //## IMPLEMENT ##//
    }
    
    // METHODS

    /**
    Adds two complex numbers.
    
    - parameter c1: Complex number to add to
    - parameter c2: Complex number to be added
    
    - returns: The result of c1 + c2.
    */
    static func add(c1: Complex, to c2: Complex) -> Complex {
        //## IMPLEMENT ##//
    }
    
    /**
    Subtract a complex number from a complex number.
    
    - parameter c1: Complex number to subtract
    - parameter c2: Complex number to subtract from
    
    - returns: The result of c2 - c1.
    */
    static func subtract(c1: Complex, from c2: Complex) -> Complex {
        //## IMPLEMENT ##//
    }
    
    /**
    Multiply a complex number by a complex number.
    
    - parameter c1: Complex number to multiply
    - parameter c2: Complex number to multiply by
    
    - returns: The result of c1*c2.
    */
    static func multiply(c1: Complex, by c2: Complex) -> Complex {
        //## IMPLEMENT ##//
    }
    
    /**
    Divide a complex number by a complex number.
    
    - parameter c1: Complex number to divide
    - parameter c2: Complex number to divide by
    
    - returns: The result of c1/c2.
    */
    static func divide(c1: Complex, by c2: Complex) -> Complex {
        //## IMPLEMENT ##//
    }
}

/**
+ operator between two Complex numbers
*/
func +(c1: Complex, c2: Complex) -> Complex {
    //## IMPLEMENT ##//
}

/**
- operator between two Complex numbers
*/
func -(c1: Complex, c2: Complex) -> Complex {
    //## IMPLEMENT ##//
}

/**
* operator between two Complex numbers
*/
func *(c1: Complex, c2: Complex) -> Complex {
    //## IMPLEMENT ##//
}

/**
/ operator between two Complex numbers
*/
func /(c1: Complex, c2: Complex) -> Complex {
    //## IMPLEMENT ##//
}

/**
+ operator between a Complex number and a Float
*/
func +(c: Complex, x: Float) -> Complex {
    //## IMPLEMENT ##//
}

/**
- operator between a Complex number and a Float
*/
func -(c: Complex, x: Float) -> Complex {
    //## IMPLEMENT ##//
}

/**
* operator between a Complex number and a Float
*/
func *(c: Complex, x: Float) -> Complex {
    //## IMPLEMENT ##//
}

/**
/ operator between a Complex number and a Float
*/
func /(c: Complex, x: Float) -> Complex {
    //## IMPLEMENT ##//
}

The code you're given is an interface for the Complex class. You have to implement all the internal logic. Recall, that Swift doesn't separate interface from implementation, and so you'll be filling in the code whenever it says //## IMPLEMENT ##// (which is pretty much everywhere). The magnitude property should return the magnitude of the number—the formulas above include one for the magnitude. The string representation of the number (i.e., the description property) should be such that complex number with real value of 3.1 and imaginary value of 0.4 is represented by the string "3.1+0.4i". Note that the imaginary part if followed by "i" to indicate that it's imaginary. If imaginary part happens to be negative, that is -0.4, the number would be represented as "3.1-0.4i". The rest of the methods should be self explanatory. If you're not sure how to go about this, follow the example of the Fraction class from the previous lab—the internal logic is a bit different, but overall implementations have many similarities.

Once you're done with the implementation, test it by running the following program from main.swift:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
import Foundation

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var x: Complex = Complex(real: 1.2, imag: -3.6)
//Show new object's state
print("x=\(x)")

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var y: Complex = Complex(real: -0.5, imag: 2.3)
//Show new object's state
print("y=\(y)")

//Show the result of addition of complex objects
print("x+y=\(x+y)")
//Show the result of subtraction of complex objects
print("x-y=\(x-y)")
//Show the result of multiplication of complex objects
print("x*y=\(x*y)")
//Show the result of dividion of complex objects
print("x/y=\(x/y)")

//Show the result of addition of complex object to a float
print("x+3.0=\(x+3.0)")
//Show the result of subtraction of a float from a complex object
print("x-2.1=\(x-2.1)")
//Show the result of multiplication of complex objects by a float
print("x*7.5=\(x*7.5)")
//Show the result of dividion of complex objects by a float
print("x/4.2=\(x/4.2)")

Compile and run. Do you get the following output?

x=1.2-3.6i
y=-0.5+2.3i
x+y=0.7-1.3i
x-y=1.7-5.9i
x*y=7.68+4.56i
x/y=-1.60289-0.173285i
x+3.0=4.2-3.6i
x-2.1=-0.9-3.6i
x*7.5=9.0-27.0i
x/4.2=0.285714-0.857143i

If not, keep working on your program—you've probably messed up somewhere or missed something. If you're still having trouble, try to come up with other test cases that might help you with debugging.

Reference assignment

Implementing the Complex class was just the first part of this exercise. Next, you're going to examine what happens when an object is assigned to another. Add the following code to main.swift:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
import Foundation

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var x: Complex = Complex(real: 1.2, imag: -3.6)
//Show new object's state
print("x=\(x)")

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var y: Complex = Complex(real: -0.5, imag: 2.3)
//Show new object's state
print("y=\(y)")

//Declare a new Complex reference and set it equal
//to another object's reference
var z: Complex = x
//Change the new object's state
z.imag = 1.0 
//Show the new object's state
print("z=\(z)")

//Show the result of addition of complex objects
print("x+y=\(x+y)")
//Show the result of subtraction of complex objects
print("x-y=\(x-y)")
//Show the result of multiplication of complex objects
print("x*y=\(x*y)")
//Show the result of dividion of complex objects
print("x/y=\(x/y)")

//Show the result of addition of complex object to a float
print("x+3.0=\(x+3.0)")
//Show the result of subtraction of a float from a complex object
print("x-2.1=\(x-2.1)")
//Show the result of multiplication of complex objects by a float
print("x*7.5=\(x*7.5)")
//Show the result of dividion of complex objects by a float
print("x/4.2=\(x/4.2)")

In this code a new Complex variable, z, is declared and is set equal to x, which has been instantiated earlier on. Given that object x has been initialised to represent number "1.2-3.6i" and the fact that the value of the z.imag is changed to 1.0, what do you expect the final value of z to be? If you answered "1.2+1.0i", then you're correct.

The stored properties of the Complex class have not been made private nor constant on purpose, just so you can carry out this test.

Run the program to confirm that z is "1.2+1.0i". Here's the output I got:

x=1.2-3.6i
y=-0.5+2.3i
z=1.2+1.0i
x+y=0.7+3.3i
x-y=1.7-1.3i
x*y=-2.9+2.26i
x/y=0.306859-0.588448i
x+3.0=4.2+1.0i
x-2.1=-0.9+1.0i
x*7.5=9.0+7.5i
x/4.2=0.285714+0.238095i

Indeed, z is what it ought to be. However, did you notice something else? Take a look at the result of all the operations on x following the creation of z—they're different from before. Add the following line to main.swift to check the state of x after the modification of z:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
import Foundation

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var x: Complex = Complex(real: 1.2, imag: -3.6)
//Show new object's state
print("x=\(x)")

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var y: Complex = Complex(real: -0.5, imag: 2.3)
//Show new object's state
print("y=\(y)")

//Declare a new Complex reference and set it equal
//to another object's reference
var z: Complex = x
//Change the new object's state
z.imag = 1.0 
//Show the new object's state
print("z=\(z)")
//Show the original object's state
print("x=\(x)")

//Show the result of addition of complex objects
print("x+y=\(x+y)")
//Show the result of subtraction of complex objects
print("x-y=\(x-y)")
//Show the result of multiplication of complex objects
print("x*y=\(x*y)")
//Show the result of dividion of complex objects
print("x/y=\(x/y)")

//Show the result of addition of complex object to a float
print("x+3.0=\(x+3.0)")
//Show the result of subtraction of a float from a complex object
print("x-2.1=\(x-2.1)")
//Show the result of multiplication of complex objects by a float
print("x*7.5=\(x*7.5)")
//Show the result of dividion of complex objects by a float
print("x/4.2=\(x/4.2)")

Run the program and you should see the following output:

x=1.2-3.6i
y=-0.5+2.3i
z=1.2+1.0i
x=1.2+1.0i
x+y=0.7+3.3i
x-y=1.7-1.3i
x*y=-2.9+2.26i
x/y=0.306859-0.588448i
x+3.0=4.2+1.0i
x-2.1=-0.9+1.0i
x*7.5=9.0+7.5i
x/4.2=0.285714+0.238095i

The state of x has changed along with the state of z. Do you know why? Recall that in Swift objects are reference types (as opposed to value types). This means, that variables of a class type are references to objects of that class. In the above program, x, y and z are references to Complex objects. Whereas x and y refer to different instances of Complex objects, assignment z=x sets the references equal—z references the same object that x does. Changing the object state through one reference affects what is seen by both x and z. One way to verify this is to use Xcode debugger. Create a breakpoint right on the line z.imag = 1.0 by double clicking at the line number. Run the program—it should stop right on the breakpoint, just before z is changed. If the values of the variables are not shown in the Debug window, make sure to select "All variables, Registers, Globals and Statistics" in its bottom-left corner—the screenshot below shows where to find the setting.

Once you can see the variables, scroll down to find x, y and z. The number beside each of them show the memory address of the object they reference. You can examine the state of the referenced object by clicking the expand arrow next to reference. The value for the address of x and z in your debugger will be different to the one shown in these screenshot, but it will still be the same address between the two references. That means, they point to the same object. Next, click the step over button, to step over the statement that changes the value of z.imag.

The program will execute just one line, z.imag = 1.0 , and stop showing you the program state just after that change. Notice that the imag value for both x and z has changed—of course it must since references point to the same object. From that point on, whenever computation on x is preformed, its modified state is used. Finish running the program by pressing the continue button.

Object copy

Having multiple references to an object is quite useful—for passing object references as parameters to functions, for instance. But there will be also situations when you will want two distinct instances of the same object, so that one can be modified without affecting the other. By convention, creation of a new object with copied state of another is done with a copy method. This method returns a new instance of the class with its state being the copy of the object on which the method was invoked. The signature of this method for the Complex class will be "func copy() -> Complex". Go ahead and implement that method in Complex.swift so it does what it's supposed to.

Next, modify main.swift so that z is assigned to a copy of x like so:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
import Foundation

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var x: Complex = Complex(real: 1.2, imag: -3.6)
//Show new object's state
print("x=\(x)")

//Declare a new reference to a Complex object and
//instantiate a new Complex object
var y: Complex = Complex(real: -0.5, imag: 2.3)
//Show new object's state
print("y=\(y)")

//Declare a new Complex reference and set it equal
//to return value of another object's copy method
var z: Complex = x.copy()
//Change the new object's state
z.imag = 1.0 
//Show the new object's state
print("z=\(z)")
//Show the original object's state
print("x=\(x)")

//Show the result of addition of complex objects
print("x+y=\(x+y)")
//Show the result of subtraction of complex objects
print("x-y=\(x-y)")
//Show the result of multiplication of complex objects
print("x*y=\(x*y)")
//Show the result of dividion of complex objects
print("x/y=\(x/y)")

//Show the result of addition of complex object to a float
print("x+3.0=\(x+3.0)")
//Show the result of subtraction of a float from a complex object
print("x-2.1=\(x-2.1)")
//Show the result of multiplication of complex objects by a float
print("x*7.5=\(x*7.5)")
//Show the result of dividion of complex objects by a float
print("x/4.2=\(x/4.2)")

Hopefully you still have the breakpoint set. Run the program again. This time, when execution stops just before z.imag is changed, you'll notice in the variable watch window that x and z reference different addresses. That means, they refer to different objects, though the contents of both objects are the same.

If you step over the next line, you'll notice that this time z.imag has changed, but x.imag didn't.

Press the continue button, and you should get the results for computations with the original value of x

x=1.2-3.6i
y=-0.5+2.3i
z=1.2+1.0i
x=1.2-3.6i
x+y=0.7-1.3i
x-y=1.7-5.9i
x*y=7.68+4.56i
x/y=-1.60289-0.173285i
x+3.0=4.2-3.6i
x-2.1=-0.9-3.6i
x*7.5=9.0-27.0i
x/4.2=0.285714-0.857143i