S.Moore
S.Moore

Reputation: 1286

Custom operator to simplify If-Let

I would like to simplify the constant need to do

if let firstName = firstName {
     self.name = firstName
}

A possible custom, generic operator to do this would be

infix operator ?= {}
func ?= <T>(inout left: T, right: T?) {
    if let right = right {
        left = right
    }
}

to simplify the previous example to

self.name ?= firstName

This creates an issue where if firstName's value is nil, then Swift will wrap the value in an optional.

var name: String? = "Bob"
var firstName: String? = nil

self.name ?= firstName

print(self.name) 
/* 
     prints "nil" since it is wrapping firstName in an optional when 
     passed in.     

     E.g. Optional<nil> gets unwrapped to nil in function and assigned
*/

Any possible fix to the custom operator? I attempted to limit the left-hand parameter to not be an optional but that is not possible with generic's type constraints.

Upvotes: 6

Views: 701

Answers (2)

Sunil Sharma
Sunil Sharma

Reputation: 2689

I found a interesting way to reduce the overhead of if let by Nil Coalescing Operator

The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that is stored inside a.

In short - nonOptional = optional ?? someDefaultValue
E.g. -

let defaultColorName = "red"
var userDefinedColorName: String?   // optional variable

var colorNameToUse = userDefinedColorName ?? defaultColorName

So, here if userDefinedColorName is nil then defaultColorName will be get assighed to non-optional variable colorNameToUse.
For complete reference check this Swift documentation

Upvotes: 0

Hamish
Hamish

Reputation: 80821

The problem (as you've correctly identified) is that because the left hand side argument is of type T, when you pass an optional into it T will be inferred to be Optional<Whatever>. Because the right hand side argument is T? (and because types can be freely promoted to optionals), it will infer the type to be Optional<Optional<Whatever>>, leading to the confusing double wrapping you're observing.

The solution is to add an overload to deal with the situation when the left hand side argument is also an optional.

infix operator ?= {}
func ?= <T>(inout left: T, right: T?) {
    if let right = right {
        left = right
    }
}

// overload to deal with an optional left handed side
func ?= <T>(inout left: T?, right: T?) {
    if let right = right {
        left = right
    }
}

(Note that in Swift 3, inout should appear before the parameter type)

Now if you use this operator with an optional as the left handed argument, Swift will use the overloaded version instead of the original version, as it'll always favour the more type-specific signature. This means that the right hand side won't get wrapped in a double optional, as it's now of the exact same type as the left hand side argument.

var name: String? = "Bob"
var firstName: String? = nil

name ?= firstName

print(name) // prints: Optional("Bob")

Note that this is similar to what the ?? does, it has two definitions to deal with one side being optional, one side being non-optional & both sides being optional in order to avoid the generation of double wrapped optionals:

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T) rethrows -> T

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T?) rethrows -> T?

Upvotes: 11

Related Questions