Reputation: 535201
I got a surprise today while looking at another SO question:
let s = "1,a"
let arr = s.split(separator: ",")
let result = arr.compactMap{Int($0)} // ok
let result2 = arr.compactMap(Int.init) // error
Why is line 3 legal but line 4 is not? I would have thought these two ways of saying "coerce the incoming parameter to Int if possible" would be completely equivalent.
I understand that line 4 is choking on the Subsequence, and I see how to get out of the difficulty:
let result2 = arr.map(String.init).compactMap(Int.init) // ok
What I don't understand is why they both don't choke in the same way.
Upvotes: 3
Views: 112
Reputation:
I tried looking for the Swift forum post where someone on the core team explained this, but sorry, I couldn't find it. You can go asking there and get clarification on this point:
Default arguments don't actually produce overloads.
Instead, using default arguments at call site is syntactic sugar for using all arguments. The compiler inserts the defaults for the ones you don't use.
You cannot use functions with default arguments as closures with simplified signatures. You have to wrap them in new closures, as you demonstrated in your question.
func ƒ(_: Int = 0) { }
let intToVoid: (Int) -> Void = ƒ // compiles
// Cannot convert value of type '(Int) -> ()' to specified type '() -> Void'
let voidToVoid: () -> Void = ƒ
Methods with different default argument patterns, that look the same at call site, are not considered overrides.
class Base {
func ƒ(_: Any? = nil) -> String { "Base" }
}
final class Derived: Base {
// No `override` required.
func ƒ() -> String { "Derived" }
}
Base().ƒ() // "Base"
Derived().ƒ() // "Derived"
(Derived().ƒ as (Any?) -> String)("argument") // "Base"
Default arguments do not allow for satisfaction of protocol requirements.
protocol Protocol {
func ƒ() -> String
}
// Type 'Base' does not conform to protocol 'Protocol'
extension Base: Protocol { }
Upvotes: 1
Reputation: 444
In fact the first version (Int($0)
) calls this initializer, which has two parameters (one of them has a default value):
@inlinable public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol
If I define a custom initializer like so, then the second example works too.
extension Int {
init?<S>(_ string: S) where S: StringProtocol {
// convert somehow, e.g: self.init(string, radix: 10)
return nil
}
}
let result2 = arr.compactMap(Int.init)
It seems to me that if I write Int.init
in the compactMap
, it can call only the exact initializer (or function), and the second parameter of the first called initializer cannot be inferred.
Another example:
func test1<S>(param1: S) -> String where S: StringProtocol {
return ""
}
func test2<S>(param1: S, defaultParam: String = "") -> String where S: StringProtocol {
return ""
}
extension Sequence {
func customCompactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
compactMap(transform)
}
}
arr.customCompactMap(test1)
arr.customCompactMap(test2) // error
I think the function references cannot hold any default values. Unfortunately I didn't find any official reference to this, but seems interesting.
Proof, last example:
func test3(param1: String, defaultParam: String = "") { }
let functionReference = test3
functionReference("", "")
functionReference("") // error
Here the functionReference's
type is (String, String) -> ()
, even though the test3
function has a default value for the second parameter. As you can see functionReference
cannot be called with only one value.
Upvotes: 4
Reputation: 49590
Looks like the Int.init
overload that accepts a Substring
has the following signature:
public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol
So, Int($0)
works because it uses the default radix
, but there isn't an Int.init(_:)
that accepts a Substring
- there's only Int.init(_:radix:)
that does - and so it fails.
But if there was one:
extension Int {
public init?<S>(_ text: S) where S : StringProtocol {
self.init(text, radix: 10)
}
}
then this would work:
let result1 = arr.compactMap(Int.init)
Upvotes: 4