Skip to content

Instantly share code, notes, and snippets.

@capnslipp
Last active February 6, 2017 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save capnslipp/9d27a8af34b6ad3402c1d5e5f2a47d0f to your computer and use it in GitHub Desktop.
Save capnslipp/9d27a8af34b6ad3402c1d5e5f2a47d0f to your computer and use it in GitHub Desktop.
Swift extension for `=??` and `??=` nil-coalescing-assignment operators
/// @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
}
}
}
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