Last active
February 6, 2017 19:28
-
-
Save capnslipp/9d27a8af34b6ad3402c1d5e5f2a47d0f to your computer and use it in GitHub Desktop.
Swift extension for `=??` and `??=` nil-coalescing-assignment operators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @creator: Slipp Douglas Thompson | |
/// @license: WTFPL | |
/// @purpose: A concise operator syntax for assigning to a non-`Optional` from an `Optional`. | |
/// @why: | |
/// Sometimes in Swift you want to assign only if the RHS is non-`nil`. | |
/// Say you have `someArg:Int?` and `_someIvar:Int`. You could use a single-line `if`: | |
/// `if someArg != nil { _someIvar = someArg! }` | |
/// Or you could use a ternary or nil-coalesce: | |
/// `_someIvar = someArg != nil ? someArg! : _someIvar` | |
/// `_someIvar = someArg ?? _someIvar` | |
/// But each of those are a bit messy for a simple maybe-assignment. | |
/// @usage: | |
/// 1. Add `NilCoalescingAssignmentOperators.swift` to your project. | |
/// 2. Use like this: `_someIvar ??= someArg` (given `_someIvar:Int`/`someArg:Int?`, or given `_someIvar:Int?`/`someArg:Int`). | |
/// @interwebsouce: https://gist.github.com/capnslipp/9d27a8af34b6ad3402c1d5e5f2a47d0f | |
// MARK: =?? Operator | |
infix operator =?? : AssignmentPrecedence | |
/// Assigns only when `rhs` is non-`nil`.. | |
/// - Remark: effectively `lhs = rhs ?? lhs` _(skipping same-value assignments)_ | |
public func =??<Wrapped>(lhs:inout Wrapped, rhsClosure:(@autoclosure ()throws->Wrapped?)) rethrows { | |
let rhs:Wrapped? = try rhsClosure() | |
if rhs != nil { | |
lhs = rhs! | |
} | |
} | |
/// Assigns only when `rhs` is non-`nil`. | |
/// - Remark: effectively `lhs = (rhs ?? lhs) ?? nil` _(skipping same-value assignments)_ | |
public func =??<Wrapped>(lhs:inout Wrapped?, rhsClosure:(@autoclosure ()throws->Wrapped?)) rethrows { | |
let rhs:Wrapped? = try rhsClosure() | |
if rhs != nil { | |
lhs = rhs | |
} | |
} | |
/// Assigns only when `rhs` is non-`nil`. | |
/// - Remark: effectively `lhs = (rhs ?? lhs) ?? nil` _(skipping same-value assignments)_ | |
public func =??<Wrapped>(lhs:inout Wrapped!, rhsClosure:(@autoclosure ()throws->Wrapped?)) rethrows { | |
let rhs:Wrapped? = try rhsClosure() | |
if rhs != nil { | |
lhs = rhs | |
} | |
} | |
// MARK: ??= Operator | |
infix operator ??= : AssignmentPrecedence | |
// FIXME: The following two variants are commented-out because Swift (3.0.2)'s type inference will apparently auto-promote a `Wrapped` type returned from a closure to `Wrapped?`, then get confused that we have specializations for both `Wrapped` & `Wrapped?`. | |
// Without commenting these out, we're stuck with explicitly typing any closures used as the RHS. | |
// With these commented out (using the specializations with always-`Wrapped?` RHSes), we just have to deal with the additional inefficiency of promoting the RHS's `Wrapped` to `Wrapped?` then doing a superfluous `rhs != nil` check. | |
///// Assigns only when `lhs` is `nil`. | |
///// - Remark: effectively `lhs = lhs ?? rhs` _(skipping same-value assignments)_ | |
//public func ??=<Wrapped>(lhs:inout Wrapped?, rhsClosure:(@autoclosure ()throws->Wrapped)) rethrows { | |
// if lhs == nil { | |
// let rhs:Wrapped = try rhsClosure() | |
// lhs = rhs | |
// } | |
//} | |
///// Assigns only when `lhs` is `nil`. | |
///// - Remark: effectively `lhs = lhs ?? rhs` _(skipping same-value assignments)_ | |
//public func ??=<Wrapped>(lhs:inout Wrapped!, rhsClosure:(@autoclosure ()throws->Wrapped)) rethrows { | |
// if lhs == nil { | |
// let rhs:Wrapped = try rhsClosure() | |
// lhs = rhs | |
// } | |
//} | |
/// Assigns only when `lhs` is `nil` (and `rhs` is non-`nil`). | |
/// - Remark: effectively `lhs = (lhs ?? rhs) ?? nil` _(skipping same-value assignments)_ | |
public func ??=<Wrapped>(lhs:inout Wrapped?, rhsClosure:(@autoclosure ()throws->Wrapped?)) rethrows { | |
if lhs == nil { | |
let rhs:Wrapped? = try rhsClosure() | |
if rhs != nil { | |
lhs = rhs | |
} | |
} | |
} | |
/// Assigns only when `lhs` is `nil` (and `rhs` is non-`nil`). | |
/// - Remark: effectively `lhs = (lhs ?? rhs) ?? nil` _(skipping same-value assignments)_ | |
public func ??=<Wrapped>(lhs:inout Wrapped!, rhsClosure:(@autoclosure ()throws->Wrapped?)) rethrows { | |
if lhs == nil { | |
let rhs:Wrapped? = try rhsClosure() | |
if rhs != nil { | |
lhs = rhs | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct Creamy | |
{ | |
public enum Flavor { case vanilla, strawberry, cherry, lemon, boysenberry, rutabaga } | |
public init(amount:Int?=nil, flavor:Flavor?=nil) { | |
self.amount ??= amount | |
self.flavor ??= flavor | |
} | |
public var amount:Int = 1 | |
public var flavor:Flavor = .vanilla | |
} | |
let summerDayAtTheFair = Creamy(amount: 5) | |
print(summerDayAtTheFair) // prints `Creamy(amount: 5, flavor: Creamy.Flavor.vanilla)` | |
let summerNightAtTheFair = Creamy(flavor: .rutabaga) | |
print(summerNightAtTheFair) // prints `Creamy(amount: 1, flavor: Creamy.Flavor.rutabaga)` | |
struct Fried | |
{ | |
public enum OnAStick { case hotDog, pickle, banana, candyBar, soda } | |
public init(onAStick:OnAStick, aLa:Creamy?=nil) { | |
self.onAStick = onAStick | |
_aLa = aLa | |
} | |
public let onAStick:OnAStick | |
private var _aLa:Creamy! | |
public var aLa:Creamy { | |
mutating get { | |
_aLa ??= Creamy(amount: 2) | |
return _aLa | |
} | |
} | |
} | |
var anotherDayAtTheFair = Fried(onAStick: .pickle) | |
print(anotherDayAtTheFair.aLa) // prints `Creamy(amount: 2, flavor: Creamy.Flavor.vanilla)` | |
print(anotherDayAtTheFair) // prints `Fried(onAStick: Fried.OnAStick.pickle, _aLa: Creamy(amount: 2, flavor: Creamy.Flavor.vanilla))` | |
var anotherNightAtTheFair = Fried(onAStick: .candyBar, aLa: Creamy(flavor: .cherry)) | |
print(anotherNightAtTheFair) // prints `Fried(onAStick: Fried.OnAStick.candyBar, _aLa: Creamy(amount: 1, flavor: Creamy.Flavor.cherry))` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment