The Problem
As of now, packages which follow Semantic Versioning cannot add new parameters which have default values to existing methods, without breaking their public API and requiring a major Sem-Ver version bump.
Why?
Imagine you have a function like this:
// MARK: Your Public API
public func doSomething(value: String) -> String {
value
}
// MARK: A User's Code
let values: [String] = ["a", "b", "c"]
_ = values.map(doSomething)
_ = values.map(doSomething(value:))
_ = values.map({ doSomething(value: $0) })
This is all fine and compiles without any problems.
Now imagine you want to add a new parameter to the function.
You know that'll be API-breaking, so you decide to provide a default value to the new parameter:
// MARK: Your Public API
public func doSomething(value: String, anotherValue: String = "") -> String {
value + anotherValue
}
Little do you know, that still breaks the public API and requires a major Sem-Ver version bump.
Your user's code no longer compiles:
/// Error: Cannot convert value of type '(String, String) -> String' to expected argument type '(String) throws -> String'
_ = values.map(doSomething)
/// Error: Cannot find 'doSomething(value:)' in scope
/// Error: Converting non-escaping value to '(String) throws -> T' may allow it to escape
/// Error: Generic parameter 'T' could not be inferred
_ = values.map(doSomething(value:))
/// No Error
_ = values.map({ doSomething(value: $0) })
This means you can't add new parameters even with default values to any public methods, even if you're 99.9% sure no-one would even think of doing something like those values.map(doSomething(value:))
with your doSomething()
function.
This is what i could call "a lot of pain with minimal gain", because even though you know no-one would use that syntax on your method, you still have to account for it.
Who Cares?
This is quite a burden on shoulders of maintainers of packages which follow Semantic Versioning.
It can result in a lot of meaningless boilerplate code which will only grow overtime.
The Solution
I understand that probably none of these solutions are doable in Swift 5. I can wait for Swift 6.
Here are some of the ways we could solve this problem:
-
Just totally remove this syntax.
-
Remove the
values.map(doSomething)
syntax, but keepvalues.map(doSomething(value:))
.
Then the compiler can validate the function calls by expanding them to the full form (values.map({ doSomething(value: $0) })
) instead of just looking at the function signature.