It occurs to me that you can prototype this right now using a higher-order function. Well, several higher-order functions, since we don't have variadic generics.
Suppose we have this function:
func returnsNonOptional(_ a: Int, _ b: Int) -> Int {
return a + b
}
With your proposal, we could call it like this:
let a: Int? = ...
let b: Int? = ...
let c: Int? = returnsNonOptional(a?, b?)
Instead, we can write a helper function that we use like this:
let c: Int? = ignoringNils(returnsNonOptional)(a, b)
Here's the helper function:
@inlinable @inline(__always)
public func ignoringNils<A0, A1, Answer>(_ f: @escaping (A0, A1) -> Answer)
-> (A0?, A1?) -> Answer?
{
return { a0, a1 in
guard
let a0 = a0,
let a1 = a1
else { return nil }
return f(a0, a1)
}
}
It should be obvious how to extend this to more (or fewer) arguments.
If we start with a function that already returns an Optional, we probably want another helper that avoids adding another layer of Optional to the return value. It can have the same name. Thus:
func returnsOptional(_ a: Int, _ b: Int) -> Int? {
return a > 0 ? a + b : nil
}
@inlinable @inline(__always)
public func ignoringNils<A0, A1, Answer>(_ f: @escaping (A0, A1) -> Answer?)
-> (A0?, A1?) -> Answer?
{
return { a0, a1 in
guard
let a0 = a0,
let a1 = a1
else { return nil }
return f(a0, a1)
}
}
let c: Int? = ignoringNils(returnsOptional)(-1, 2)
These helpers use your proposed rule of evaluating all arguments regardless of which are nil. Example:
let c: Int? = ignoringNils(returnsOptional)(nil,
(print("hello"), 1).1)
// prints "hello"
But we can tweak the helpers to act more like the throwing expression rules by using @autoclosure:
@inlinable @inline(__always)
public func ignoringNils<A0, A1, Answer>(_ f: @escaping (A0, A1) -> Answer?)
-> (@autoclosure @escaping () -> A0?, @autoclosure @escaping () -> A1?) -> Answer?
{
return { a0, a1 in
guard
let a0 = a0(),
let a1 = a1()
else { return nil }
return f(a0, a1)
}
}
let c: Int? = ignoringNils(returnsOptional)(nil,
(print("hello"), 1).1)
// doesn't print "hello"