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)
    }
}

TOC