Write your own Swift optionals
2021, Jun 15
This article provides a summary on the Swift optional mechanism, the source code of Swift optional can be retrieved here
@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
// The compiler has special knowledge of Optional<Wrapped>, including the fact
// that it is an `enum` with cases named `none` and `some`.
/// The absence of a value.
case none
case some(Wrapped)
After read through the codes, there are some critical knowledge which is essential to construct the implementation
Knowledge Required
- attributes used:
@autoclosure
: auto wraps input into closure.@inline
: single module’s internal optimisation.@inlineable
: complier highlight to hint the function can be optimized when referred from external modules.@frozen/@unfrozen
: provide the compatibility for @unfronzen enums to expand in future, user can add a @unkown default in the switch without any warnings.
- techniques
advance operator
: customize your own prefix/infix/postfix operators.- customDebugStringConvertible.
- expressibleByNilLiteral
Use Case
Based on the source code, we can see there are several core use cases of Optionals:
- support nil coalescing ??
- support comparison function.
- support mapping a optional to another optional.
The Code
import XCTest
/// Copy the code and run it in xcode unit test.
class preparation_02: XCTestCase {
enum MyOptional<T: Equatable>: ExpressibleByNilLiteral {
case value(T)
case none
// initializer.
init(nilLiteral: ()) {
self = .none
}
init(value: T) {
self = .value(value)
}
// method overloading.
static func ??(lhs: MyOptional, rhs: @autoclosure () -> T) -> T {
switch lhs {
case .value(let t):
return t
default:
return rhs()
}
}
static func ??(lhs: MyOptional, rhs: @autoclosure () -> T?) -> T? {
switch lhs {
case .value(let t):
return t
default:
return rhs()
}
}
// method match equatable
static func ==(lhs: MyOptional<T>, rhs: MyOptional<T>) -> Bool {
switch (lhs, rhs) {
case let (.value(l), .value(r)):
return l == r
case (.none, .none):
return true
default:
return false
}
}
// method match not equatable
static func !=(lhs: MyOptional<T>, rhs: MyOptional<T>) -> Bool {
switch (lhs, rhs) {
case let (.value(l), .value(r)):
return l != r
case (.none, .none):
return false
default:
return false
}
}
// map & flatmap
public func flatMap<U>(
_ transform: (T) throws -> U?
) rethrows -> U? {
switch self {
case .value(let y):
return try transform(y)
default:
return nil
}
}
public func map<U>(
_ transform: (T) throws -> U?
) rethrows -> U? {
switch self {
case .value(let y):
return try transform(y)
default:
return nil
}
}
}
func testMyCustomizedOptional() {
// normal initialisation
let optional: MyOptional<String> = .value("test")
// support ExpressibleByNilLiteral
let optional2: MyOptional<String> = nil
let optional3: MyOptional<String> = .value("test")
// compare two optionals
let compareResult = optional == optional2
let compareResult1 = optional == optional3
print(compareResult)
print(compareResult1)
// nil coalescing
let result = optional2 ?? "default value"
print(result)
// mapping
let mappingResult = optional3.map { (value) -> String? in
// this will be called.
return "mapped value"
}
assert(mappingResult == "mapped value")
let mappingResult1 = optional2.map { (value) -> String? in
// this will not be called
return "mapped value"
}
assert(mappingResult1 == nil)
}
}