Reputation: 218
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
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