Creating a Conditional Assignment Operator in Swift

The conditional assignment operator is a concise way of assigning a value to a variable if the variable is empty. I'm aware of two languages that contain the operator -- here's how it looks in Ruby and in Coffeescript:

Ruby:

   thing = nil
   thing ||= "Something"        # "Something"
   thing ||= "Another Thing"    # "Something"

Coffeescript:

    thing = null
    thing ?= "Something"        # "Something"
    thing ?= "Another Thing"    # "Something"

Ruby and Coffeescript have it, why not Swift? In Swift you'd have to write:

    var thing : String? = nil

    if thing == nil {
        thing = "Something"       // "Something"
    }

    if thing == nil {
        thing = "Another Thing"   // "Something"
    }

The Swift version is more verbose because it expresses the two rules separately -- Rule 1, 'if thing is nil' Rule 2, 'assign the value "Something"'

Handily, temptingly, perhaps terribly, Swift lets us define our own operators! So we can build a conditional assignment operator even though it's not provided by the language.

There's probably a good debate around the clarity of custom operators. At the end I'll state my arguments in favor of this one.

Our Swift conditional assignment operator will be ??=.

So how do we define a custom operator in Swift? First we need to declare the operator:

    infix operator ??= { associativity right precedence 90 }

There are a couple things here that need explanation:

infix means this operator will be used between two operands, like the equals sign (a = b) or addition operator (a + b).

precedence 90 is a low precedence, and it's the same as other assignment operators. For example, =, *=, += and other assignments all have a precedence of 90. This means that any other operations will happen first, and then the assignment will happen. You wouldn't want a = b + c to first assign a = b, you want it to evaluate b + c first and then assign the result to a.

associativity right will group the operations on the right if they have the same precedence, and is also the same associativity that the other assignment operators have.

Now that we've declared the operator, we have to implement it:

    func ??= <T: Any>(inout left: T?, right: T?) {
        if left == nil {
            left = right
        }
    }

The implementation is pretty brief and the body looks very similar to the Swift code I had originally, but we've generalized it to work with any type and any two values. Some things to note about the implementation:

  • It uses generics to make sure the left-hand-side and right-hand-side arguments are the same type, and to ensure it's safe to perform the assignment.
  • The left-hand-side parameter is inout. It needs to be a mutable value because we're going to try to assign to it.

Pretty simple! Now we can use it:

   var thing : String? = nil
   thing ??= "Something"        // "Something"
   thing ??= "Another Thing"    // "Something"

I think custom operators can be a bad idea if they're too foreign-looking or complex. However, I think this operator avoids that trap for a few reasons:

  • It's kind of similar in appearance and function to the nil coalescing operator, and you could say it's the 'compound assignment equivalent' of nil coalescing. (compound assignment is the term for operators like *=, +=, etc., where one operator performs both assignment and another operation.)
  • The implementation is simple, so shouldn't be difficult to understand for people who are unfamiliar with it.
  • It's concise and has precedence in other mainstream languages.
  • I think some custom operator implementations I've seen would be more clear if they were left as regular functions. This one could be implemented as a function, but I don't see a way to obtain the same clarity -- it would look like something like ... assignIfNil(&thing, "Something") maybe?

Comments:

rss

blog powered by Pelican