znerol
znerol

Reputation: 218

Swift 4 extension function on generic protocol which takes a generic function as parameter

swift --version
Swift version 4.1 (swift-4.1-RELEASE)
Target: x86_64-unknown-linux-gnu

Given a simple protocol which defines a generic producer (Source):

protocol Source {
    associatedtype T

    func produce() -> T
}

And a mapping capable of converting between source types:

struct Mapping<T, U : Source> : Source {
    typealias Transformation = (U.T) -> T

    private let upstream : U
    private let block : Transformation

    init(_ upstream: U, _ block : @escaping Transformation) {
        self.upstream = upstream
        self.block = block
    }

    func produce() -> T {
        return block(upstream.produce())
    }
}

And a sample source which produces static text:

struct TextSource : Source {
    private let text : String

    init(_ text: String) {
        self.text = text
    }

    func produce() -> String {
        return text
    }
}

I can use this to, e.g., count characters...

let t = TextSource("Hi!")
let f = Mapping(t, { (text: String) -> Int in
    return text.count
})

print(f.produce()) // output: 3

But I'd rather like to use a generic map extension function on Source such that transformations can be chained, e.g.:

let t = TextSource("Hi!").map { (text: String) -> Int in
    return text.count
}

Approach A

extension Source {
    func map<T, U : Source>(_ block: @escaping Mapping<T, U>.Transformation) -> Source {
        return Mapping(self, block)
    }
}

That is rejected by the swift compiler:

error: generic parameter 'U' is not used in function signature
    func map<T, U : Source>(_ block: @escaping Mapping<T, U>.Transformation) -> Source {
                ^

Approach B

extension Source {
    func map<T>(_ block: @escaping Mapping<T, U>.Transformation) -> Source {
        return Mapping(self, block)
    }
}

In this case the compiler complains about the missing type parameter:

error: use of undeclared type 'U'
    func map<T>(_ block: @escaping Mapping<T, U>.Transformation) -> Source {
                                              ^

Question

Which type parameters and constraints need to be specified on the map extension function in order to satisfy the compiler?

Upvotes: 1

Views: 1110

Answers (1)

Palle
Palle

Reputation: 12109

You cannot use Source as a concrete return type for map because it is a protocol with an associated type requirement.

To solve this, you can have the map function return Mapping<X, Self>:

extension Source {
    func map<Result>(_ transform: @escaping (T) -> Result) -> Mapping<Result, Self> {
        return Mapping(self, transform)
    }
}

The function now has a Self requirement. The resulting Mapping type has a generic type parameter Self that is replaced by the concrete implementation of Source, e.g. Mapping or TextSource.

Upvotes: 1

Related Questions