matt
matt

Reputation: 535201

What aren't these two ways of expressing map function equivalent?

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

Answers (3)

user652038
user652038

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.

A few results of that…


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

kristofkalai
kristofkalai

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

New Dev
New Dev

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

Related Questions