I have an idea which would solve an annoyance that I run into frequently. Imagine I have a function with this signature:
func complementaryColor (to color: Color) -> Color
It returns the color which is opposite to the given color on the color wheel. I can of course call it like this:
let complement = complementaryColor(to: user.favoriteColor)
print("The complement to your favorite color is \(complement)")
This assumes, however, that the property favoriteColor
on my User
type is a non-optional Color
- but not everyone has a favorite color, or at least not everyone is comfortable disclosing such information to just anyone. I often find myself in a situation like this, where the parameter I want to pass in is optional, and my desired behavior is that if the parameter is nil
the function will return nil
. To handle this I sometimes create an overload for the function that I want to use this way:
func complementaryColor (to color: Color?) -> Color? {
guard let color = color else { return nil }
return complementaryColor(to: color)
}
The second line in this function will invoke the original function, because the value is non-optional and the original function accepts a non-optional parameter, which causes it to take precedence (therefore, no infinite recursion). Instead of having to use this overload hack, I would love to be able to invoke the original (non-optional) function like this:
guard let complement = complementaryColor(to: user.favoriteColor?) else { return }
print("The complement to your favorite color is \(complement)")
where user.favoriteColor
is now of the type Color?
, which is more reasonable than the rather demanding Color
which was previously its type. The language feature I'm proposing is that an optional value can be passed into a non-optional parameter position in a function or closure call if it is post-fixed by ?
- the result is that the return type becomes optional, and the whole function evaluation is skipped and nil
is returned if any of the unwrapped parameters are nil
.
I think this is in line with the current behavior of optional chaining, where optionals can be used rather freely as long as one is aware that certain parts of the code may be skipped if nil
is found (e.g., optionalValue?.doSomething(input: thisMayNeverBeEvaluated()
).
I think that if the return type of a function is already optional, then rather than turn it into a double optional it should be flattened (i.e., remain the same even when used with an unwrapped argument). Meaning that if the function is:
func returnsAnOptional (input: Bool) -> Bool?
then invoking it like this:
guard let nonOptionalBool = returnsAnOptional(input: optionalBool?) else { return }
will result in nonOptionalBool
being of type Bool
, rather than type Bool?
. If one needs to distinguish between "nil
because the unwrapped parameter was nil
" and "nil
because the function legitimately returned nil
" then that can still of course be done by explicitly unwrapping the value first:
guard let unwrappedInput = optionalBool else { handleInputBeingNil(); return }
guard let nonOptionalBool = returnsAnOptional(input: unwrappedInput) else { handleFunctionReturningNil(); return }
One way in which this is an improvement over my overload solution is that it becomes apparent at the call site that there is the possibility of skipped evaluation.
Looking forward to hearing what people think about this.