Comparing Optionals with each other

Hi,

recently I've programmed a lot of Javascript. And in JS the expressions I wanted to be evaluated were something like:

expirationDate < new Date();

Which is expirationDate is undefined returns false (which is the intended behaviour, as nil/null/undefined is neither earlier nor equal, nor later than any Date object.
I now had the same issue in Swift, I am maintaining an optional expirationDate which is compared to the current date at some positions in my code.
Unfortunately Comparable (which Date conforms to) expects non optional parameters.

So my choice was to always perform:

if let date = expirationDate {
   // compare date to Date()
} else {
   // handle the 'false' case
}

expirationDate < Date() would throw an error.

So I started creating my own operator for that scenario. (I at first used an extension on Date and overloaded the < operator with optional parameters):

infix operator <?: ComparisonPrecedence
public func <? <T>(lhs: T?, rhs: T) -> Bool where T: Comparable {
   if let leftSide = lhs {
      return leftSide < rhs
   }
   return false
}

I also added those functions for <=, >=, >, == etc. In the example of Date it makes sense as you can't tell what nil is compared to Date().
I'd argue the same for numbers or any comparison for that matter.

Do you know any example in which someOptional < someComparable should evaluate true by default if someOptional is in fact nil? If you need to default to true, you can still use the if let construct.

Any examples or counter-examples on why this is not helpful are greatly appreciated. Thanks!

-> @sharedRoutine

Optional comparison operators did exist in early Swift versions, but were removed in Swift 3. You can get some information on the change here:

Note that in your example, you can check for optionality and do the comparison in the same if:

if let date = expirationDate, date < Date() {
 // expirationDate is not nil and in the past
} else {
 // expirationDate is nil or in the future
}

You can implement that logic with expirationDate.map({ $0 < Date() }) ?? false

2 Likes

Thanks for the information!

Yeah I know I can put it in the same if statement, but the simple variable declaration is not that great:

let isStillActive: Bool = expirationDate > Date(). It would add at least another level of indentation.

But the suggestion of @DeFrenZ looks really good

The nuance I see is that if the return type from your operator is false, you don't know if it's because the comparison failed (lhs >= rhs), or if the lhs is .none. Maybe return an optional Bool should be considered ( a kind-of "tri-state" Bool) if one of the operands is T? and the operand is .none?

Another pattern I use in these cases is to provide a failing (or succeeding) default for the comparison, depending on the behavior I want. If nil should mean the subscription (or whatever) is still valid, you can write:

let isStillActive = (expirationDate ?? .distantFuture) > Date()
4 Likes
Terms of Service

Privacy Policy

Cookie Policy