// // Optionals.swift // Unilib // // Created by David James on 5/31/17. // Copyright © 2017-2021 David B James. All rights reserved. // import Foundation public protocol OptionalType { associatedtype Wrapped var optional:Wrapped? { get } } extension Optional : OptionalType { public var optional:Wrapped? { return self } } public extension Optional { /// Does this optional have a value? var exists:Bool { switch self { case .some : return true case .none : return false } } /// Cast an optional to a type or nil func `as`(_:T.Type) -> T? { return flatMap { $0 as? T } } /// Convert this optional to an array. If there /// is a wrapped element return an array of that /// element, otherwise return empty array. func toArray() -> [Wrapped] { // TODO What if wrapped is an array with items? // Would return 2D array. Should we flatten it? switch self { case .some(let wrapped) : return [wrapped] case .none : return [] } } // NOTE: Before you're tempted to alias map/flatMap, consider that they // are probably the best names at the moment. // - map() is good because it maps an optional to something non-nil // (i.e. the closure must return a non-nil value). // - flatMap() is not so good. It's still a mapping operation. However, // it can return nil (i.e. in case the mapping is not possible). // The prefix "flat" does not signify this, but I couldn't find // a suitable alternative. So, since map/flatMap are well known terms // of art, perhaps it's better to keep them as-is. // (I tried "wrap" or "unwrap" but neither convey "mapping".) // - The problem is, in code, it's not always obvious that the // thing being mapped is an optional (without option clicking it) // so that, at first glance, "map/flatMap" may imply that the // variable is a collection and not an optional. // Something like unwrapAndMap would be more clear. // NOTE: Swift 4+ "compactMap" (renamed to avoid confusion) applies only to collections. } public extension Optional where Wrapped: Sequence { var unwrap:[Wrapped.Element] { switch self { case let wrapped? : return Array(wrapped) case nil : return [] } } } // == and != on Optional already exist for comparing // optionals or optionals and non-optionals public extension Optional where Wrapped == Bool { var isDefault:Bool { self == nil } var isDefaultOrTrue:Bool { isDefault || isTrue } var isTrue:Bool { self == true } var isFalse:Bool { self == false } } // Move the following to it's own file (must make file system changes via Unilib project) precedencegroup AppendStringPrecedence { higherThan : AdditionPrecedence associativity: left } infix operator ..? : AppendStringPrecedence public extension Optional { /// Append string based on an optional value /// as returned from closure. /// If the value exists, an extra space will /// be inserted, otherwise this returns an empty string. private func appendString(_ closure:(Wrapped)->String) -> String { switch self { case let wrapped? : return " " + closure(wrapped) case nil : return "" } } /// Append string based on an optional value /// as returned from closure. /// If the value exists, an extra space will /// be inserted, otherwise this returns an empty string. static func ..?(lhs:Wrapped?, rhs:(Wrapped) -> String) -> String { return lhs.appendString(rhs) } // Why do this? ^^ // Goal: myString = firstBit + secondOptionalBit?.something // Long way: // var myString = firstBit // if let secondBit = secondOptionalBit { // myString += " " + secondBit.something // } // Short way: // let myString = firstBit + secondOptionalBit ..? { $0.something } // .. which is very close to the pseudo code above }