【问题标题】:Swift 4 extension function on generic protocol which takes a generic function as parameter通用协议上的 Swift 4 扩展函数,它以通用函数为参数
【发布时间】:2018-05-17 05:11:37
【问题描述】:
swift --version
Swift version 4.1 (swift-4.1-RELEASE)
Target: x86_64-unknown-linux-gnu

给定一个定义通用生产者的简单协议(来源):

protocol Source {
    associatedtype T

    func produce() -> T
}

以及能够在源类型之间转换的映射:

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())
    }
}

还有一个生成静态文本的示例源:

struct TextSource : Source {
    private let text : String

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

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

我可以用它来计算字符数……

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

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

但我更愿意在 Source 上使用通用的 map 扩展函数,以便可以链接转换,例如:

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

方法 A

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

这被 swift 编译器拒绝:

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

方法 B

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

在这种情况下,编译器会抱怨缺少类型参数:

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

问题

需要在map扩展函数上指定哪些类型参数和约束才能满足编译器要求?

【问题讨论】:

    标签: swift generics


    【解决方案1】:

    您不能将Source 用作map 的具体返回类型,因为它是具有关联类型要求的协议。

    要解决这个问题,您可以让map 函数返回Mapping&lt;X, Self&gt;

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

    该函数现在具有Self 要求。生成的Mapping 类型有一个泛型类型参数Self,它被Source 的具体实现所取代,例如MappingTextSource

    【讨论】:

    • 好的,非常感谢。这正是我想要的。
    • 在 Swift 5.1 中,可以指定不透明的结果类型(即 -&gt; some Source 而不是 -&gt; Mapping&lt;Result, Self&gt;。您介意更新/扩展您的答案吗?
    • 请注意,不透明的返回类型有一些限制。如果你有some Source,你将丢失关联类型T 的类型信息,所以你真的不能用它做任何事情。例如TextSource("Hi!").map {$0.count}.map {$0 + 42} 将不再编译,因为在第二个闭包中,不再可能知道T 指的是什么。所以我不认为some Source 在这里是一个好的解决方案。当 Swift 得到泛化的存在时,也许可以做这样的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-21
    • 1970-01-01
    • 2015-12-05
    • 1970-01-01
    相关资源
    最近更新 更多