New Convenient Behavior for Ternary Operator

I dislike when I have to do this:

value != nil ? self.useValue(value!) : self.doSomethingElse()

I dislike it because the type system is powerful and helpful, and using ! means turning off the type system and just winging it. However, doing this within the type system takes up a completely unnecessary number of lines and just feels like too much boilerplate:

if let value = value {
    self.useValue(value)
} else {
    self.doSomethingElse()
}

For this use case, I think it would more elegant and expressive to be able to write:

value ? self.useValue(value) : self.doSomethingElse()

I am aware of and agree with the decision to not allow if optionalValue { }, where an optional is used as a pseudo-boolean. What I'm proposing is that only with the ternary operator one is allowed to use an Optional instead of a Bool, and that in the affirmative half of the operator the name of the optional is shadowed by its unwrapped counterpart so it can be used without !.

I think this may be less dangerous than allowing an optional in an if-statement and therefore may be approvable, but also my knowledge of the issue is limited, so more than approval of this idea I'm looking for an understanding of the potentially various reasons why something like this can't or won't be done. I'm guessing that one major hurdle to this concept is the idea of treating the affirmative half of the ternary operator as a distinct scope with different variable definitions. Perhaps the way the language is built makes this thoroughly impossible...

I do think the syntax lines up with the semantics quite nicely though, given that the optional to be potentially unwrapped is followed by a ?. In the course of writing this post I'm realizing more and more that this would be such a usefully lightweight way to handle optional unwrapping. Is the main issue in terms of language design that someone might accidentally use a Bool?, forgetting that it isn't a Bool, and be expecting it to exhibit a behavior different to the reality?

Thanks for any responses.

1 Like

Just a quick not here. If I am not mistaken, @jrose once wrote in some post that it is actually not safe to unwrap value in your case. The language makes no guarantees that at the point of calling the value has not become nil there. The only safe way is to bind the value using if let… for example.

Maybe Jordan can give some insight or maybe I am just mis-remembering.

So otherwise, if the shorthand would bind the value if it is used anywhere on the right hand side of the ? that would indeed be convenient. I like it.

Edit: Could it be that the "not safe" thingy was only for bridged values from Objective-C? If only I could remember all the posts I read here…

4 Likes

I believe the primary issue with writing

value != nil ? self.useValue(value!) : self.doSomethingElse()

is the potential for a race condition. If value != nil evaluates to true, and then value is set to nil before self.useValue(value!) is evaluated, the code sample will trap.

I'm not 100% convinced an unwrapping shorthand is justified, but a warning if this anti-pattern is detected might be a good idea.


There has been some discussion in the past of more general 'pattern matching/binding expressions' which might also solve this issue along with others. One past thread I found was Unwrapping and value binding patterns in the condition of ternary expressions , but I'm pretty sure there have been others as well.

1 Like

Try this:

value.map { self.useValue($0) } ?? self.doSomethingElse()
10 Likes

If your main goal is to reduce the number of lines then you can wrap this to

if let value = value { self.useValue(value) } else { self.doSomethingElse() }

which is hard to beat because you can already write it today and it seems at least as easy to understand as the ternary. The next obvious improvement would be a way to remove the repetition of the name in if let value = value (e.g. if let value or similar shorthand), which has been discussed a lot but I'm not sure has ever made it to review. Something like that seems to me like a more likely way forward than adding special behaviour to the ternary operator that introduces confusion about optionals in boolean context.

2 Likes

Optional.map is your friend :slight_smile:

value.map(self.useValue) ?? self.doSomethingElse()
4 Likes

…also it becomes a lot more concise and easier to read if you drop the unnecessary “self.”:

value.map(useValue) ?? doSomethingElse()
5 Likes

I'm not sure we guarantee that if let-style unwrapping is atomic anyway (consider a very large optional struct), so if there's a race condition between value != nil and value! your program already has problems, formally and practically. The concern about separating the two comes when other code is synchronously run in between, as in the following:

self.value != nil ? self.performMagic(self.modifyValue(), self.value!) : self.doSomethingElse()

Swift defines evaluation order of parameters to be left-to-right*, so in this case the nil test has gone stale before the value is used. (This problem isn't limited to accessing properties but that's the clearest example.) This applies both to the ternary operator and to normal if or guard.

* with a bit of complexity around inout, but that isn't relevant here.

7 Likes

It's also an issue with computed variables, e.g.

private var hasGotProperty: Bool = false
var myProperty: Int? {
  guard !_hasGotProperty else { return nil }
  defer { hasGotProperty = true }
  return 1
}

I have an alternative solution that might be interesting. It introduces the “Optionally call” operator “.?”, which lets you write:

useValue.?(value) ?? doSomethingElse()

That is, it preserves the expected ordering of function-then-arguments, in contrast to map which has them reversed.

The implementation is a generic operator that simply calls Optional.map in the opposite order. In other words, it takes a function on the left and an optional tuple of that function’s arguments on the right, and if the tuple is not nil then it calls the function with those arguments:

Implementation
precedencegroup FunctionCallPrecedence {
  higherThan: BitwiseShiftPrecedence
  associativity: left
  assignment: false
}

infix operator .?: FunctionCallPrecedence

func .? <T, U> (f: (T)->U, x: T?) -> U? {
  return x.map(f)
}
2 Likes

@Nevin I love this direction you took the idea - it caused me to formalize an idea I've had brewing for a little while, which I just posted here: Unwrap optionals within function call

This new proposal would of course simultaneously solve the problem we're discussing here. I personally think the resulting syntax is the most elegant solution so far to the problem I raised in this post:

useValue(value?) ?? doSomethingElse()

1 Like
Terms of Service

Privacy Policy

Cookie Policy