A More Swifty Way to Check an Optional for a Nil Value

optional

(Richard L Zarth III) #1

Swift does a great job handling optional values. Initially a learning curve for new developers, the power and value of using optionals quickly becomes apparent when writing safe, thoughtful code. Swift offers up many ways to check if an optional value is currently Optional.some(Wrapped) or Optional.none (really the nil literal) and react appropriately without needing to directly check for a nil value. In fact, it seems as though most of the Swift language features surrounding optionals are specifically designed to avoid having to check for nil explicitly. This is true with optional binding (using if let, guard let, and switch statements), optional chaining (using the postfix ? operator), and the nil-coalescing operator (using the ?? operator).

However, there is still a common case where nil must be checked for manually:

var someNumber: Int?

if someNumber != nil {
    // Do something.
}

I think it would be considerably cleaner and more Swifty to be able to write the following:

var someNumber: Int?

if someNumber? {
    // Do something.
}

This can be useful in the case where someone does not care about the value potentially inside of an optional variable (i.e. optional binding wouldn't be desired here; doing so causes a compiler warning because of the unused local value), but they do care if the value is nil (or potentially not nil). Other optional-related tools also would not make sense here, such as optional chaining or the nil-coalescing operator.

I think this proposal would fit in line with other Swift syntax and make for a way to check for non-nil (non-null) values in a similar way to other programming languages. For instance, if (someNumber) in C would return false if null (also if 0, but that's beside the point here). Other languages support similar syntax as well. An advantage to using the ? in this proposal is the explicit and clear intent of the code, which is a focus in the Swift language overall.


#2

It does feel too magical, much in the same line as case _ for default case.


(Suyash Srijan) #3

I came up with a nice way (with some inspiration from Kotlin) but it's not perfect.

extension Optional {

func `let`(_ unwrapped: (Wrapped) -> Void) {
	if case let .some(value) = self {
		unwrapped(value)
	}
  }
}

let foo: Int? = nil

// Won't be executed
foo.let { unwrapped in
  print(unwrapped)
}

let baz: Int? = 0

// Will be executed
baz.let { unwrapped in
  print(unwrapped)
}

let fooBar: String? = "Hello, world!"

// More compact
fooBar.let {
  print($0)
}

(Tomáš Znamenáček) #4

IMHO that’s map:

foo.map {
    print($0)
}

As for the pitch itself, the members of the core team have said several times that pure sugar proposals would need to improve the language in a fairly significant way to be accepted. I can’t find any link at the moment, but I hope the reasoning is clear – the syntax is fairly usable as it is, so let’s prefer language stability for a while and focus on bigger fish?


(Suyash Srijan) #5

Yeah pretty close to it - although it doesn’t execute the closure if the optional is nil.

I like how you could do .let in Kotlin hence why I made this :slight_smile:


(Aaron Farnham) #6

This is flatMap on optional. Try this in a playground... it does not execute the closure if nil:

var n: Int? = 1
n.flatMap { print($0) }
n = nil
n.flatMap { print($0) }

(Suyash Srijan) #7

Oh!


(Jeremy David Giesbrecht) #8

Back to the original pitch, I also find it appears more “magical” and confusing. Postfix ? already appears everywhere in type names and there it causes optionality. (T?)

Except for ? being disallowed from starting a postfix operator, this pitch really isn’t a language‐level change. You can basically already use it today if you find it useful:

postfix operator .?
public postfix func .?<T>(operand: T?) -> Bool {
    return operand != nil
}

var optional: String? = "Hello, world!"

if optional.? {
    print("It’s there.")
}

(Richard L Zarth III) #9

While I do appreciate the recommendations from suyashsrijan, zoul, afarnham, and SDGGiesbrecht for implementations that can be done currently through extensions, map/flatMap, and custom operators (which can all be added on a per-project basis), I do believe that this functionality is something that could help Swift as a whole. While this can fall into the category of "syntactic sugar," I don't believe that it is something that should not be considered.

Swift goes through great lengths at times to avoid programmers having to directly reference nil, and I think that this proposal does a justice to continue with that practice. While similar functionality can be added with the recommendations above, I believe that this change should be easily available to all Swift developers in order to make code that is clear and concise in purpose. This change adds a common functionality that is present in many other languages (including Objective-C) without the need for extensions and such. So adding this as a standard Swift feature feels very natural in my opinion.


(^) #10

I just don’t see much of an actual use for this. 99% of the time when you want to check if something is not nil, you also want to do something with the value, so if let or guard let is appropriate. It’s a lot less common to want to assert that something is nil while not caring about the value if it’s not.


#11

I'd push back on this idea a little bit. It's one way of looking at it, but I have sense that it misrepresents an intentional design choice made (or, at least, debated publicly) in the past.

I'd say that Swift rather tries to eliminate the need to use two statements when one would do (being more concise AND expressive at the same time). In the area of optionals, this plays out as syntax for using non-nil values that eliminate the need for a separate check for nil. The "disappearance" of the nil keyword isn't the point, it's more of a side effect.

It's much harder to argue that eliminating the nil from a single statement alone (e.g. adding syntax to eliminate the == nil part of the test as you're proposing) really ups the ante on conciseness and expressiveness.

That's aside from the issue, already raised earlier in this thread, of blurring what ? means, by smearing the semantics of whether that sigil represents the elimination of optionality or the introduction of optionality into any given syntactic scenario.


#12

I like this point from the zen of python a lot, which this pitch will violate

There should be one-- and preferably only one --obvious way to do it.

foo != nil is simple and obvious. Don't introduce new syntax that people have to learn, obscures the meaning, and forces people to think about what to choose.


(Tomáš Znamenáček) #13

This is a very good point. I am currently doing a job in JavaScript (+ Flow) and the complex semantics of seemingly trivial expressions are a constant headache. Here, explicit comparison to nil or explicit binding of a non-nil variable is IMHO as obvious as possible. That’s a win. With the proposed x? sugar this is no longer the case – one may wonder, for example, if the expression has some kind of truth coercion going on, like .some(false) evaluating to false.


(Jeremyabannister) #14

I agree very much with this response by @QuinceyMorris. Using nil explicitly is not actually something which Swift aims to avoid - its avoidance is a natural consequence in some situations. In others, however, (for example assignment) using nil explicitly is the standard, and a non-objectionable one in my estimate.

I think this last part though is mixing up concepts. The only realm in which optionality can be "introduced" or "eliminated" is types. The type is where the decision of optionality is made, and ? when applied to type names is unambiguous - it makes it optional.

When applied to instances, ? means to unwrap the value. This proposal does not violate that exactly - in this case as well it means to unwrap - but this proposal wants the return type of the postfix ? operator to be Bool when used in an if statement, rather than its normal return type of Wrapped. While the compiler can technically do whatever the Swift team wants it to do, I think it is bad design to have the same operator return different types depending on which language construct it is used in.


(Max Howell) #15

This matches the same syntax for switch:

switch Optional(1) {
case 1?:
    //…
case .some:
    // other integer
case nil:
    //…
}

So it's consistent (not that I ever liked this syntax particularly).

Also someone should note that using map or flatMap in the suggested manners is strictly misuse of functional paradigms; like I’d make the Void conformances of both of these @unavailable if I was in charge. Having said this I've used them like this in real life since it can be quite elegant.


(Xiaodi Wu) #16

No, it doesn't match that syntax. You're showing case matching, where the ? operator is suffixed to the case, not the value being switched over. The corresponding syntax is already available for if statements: if case 1? = Optional(1) { ... }.


(Max Howell) #17

Sorry, I figured my point was clear: people would grok the proposal if it was added.


(Alexis Gallagher) #18

I suppose I disagree with the premise. I don’t see anything unclean or unSwifty about

It is simple, explicit, and easily understood.

In fact, I think it fits Swift’s goal of progressive disclosure that you can understand the intent of that condition correctly, even before you might learn that Optional is just an enum with associated values, and nil is a somewhat magical polymorphic literal.

For these reasons I would see the proposed change as a step backwards.


(Amnykon) #19

I think if value? {then()} contains some uncertainty if value is of type Bool?.

if value is nil:
it is clear that then() would not be executed.

if value is true,
it is clear that then() would be executed.

if value is false,
should then() execute?

In this case, I prefer if value == true {then()} as this provides more clarification.

I have a similar Idea I need to pitch for optional chaining of function arguments: function(value?) similar to function(value!), that this pitch would conflict with unless value type is Bool?.