Swift 4:扩展Array <optional <sometype <t >>>以产生Array <optional <t >>可能吗?

时间:2018-06-23 09:13:28

标签: arrays swift generics optional swift-extensions

在Swift 4中,我有一个具有以下基本前提的自定义结构:

包装器结构,可以包含符合BinaryInteger的任何类型,例如Int,UInt8,Int16等。

protocol SomeTypeProtocol {
    associatedtype NumberType

    var value: NumberType { get set }
}

struct SomeType<T: BinaryInteger>: SomeTypeProtocol {
    typealias NumberType = T

    var value: NumberType
}

以及Collection的扩展名:

extension Collection where Element: SomeTypeProtocol {
    var values: [Element.NumberType] {
        return self.map( { $0.value })
    }
}

例如,这很好用:

let arr = [SomeType(value: 123), SomeType(value: 456)]

// this produces [123, 456] of type [Int] since literals are Int by default
arr.values

我想做的是完全相同的事情,但对于SomeType<T>?

let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]

// this doesn't work, obviously
arr.values

// but what I want is this:
arr.values // to produce [Optional(123), Optional(456)]

我已经尝试了许多尝试来解决这个问题,并进行了大量研究,但我希望Swift的资深人士中的任何一位智者都可以对此有所了解。

这是我设想的样子,但这不起作用:

extension Collection where Element == Optional<SomeType<T>> {
    var values: [T?] {
        return self.map( { $0?.value })
    }
}

这是不使用泛型即可达成目标的笨拙方法,并且有效:

extension Collection where Element == Optional<SomeType<Int>> {
    var values: [Int?] {
        return self.map( { $0?.value })
    }
}

let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
arr.values // [Optional(123), Optional(456)]

但是它需要为符合BinaryInteger的每个已知类型手动编写扩展名,并且在不手动更新代码的情况下不会自动包含采用BinaryInteger的将来类型。

// "..." would contain the var values code from above, copy-and-pasted
extension Collection where Element == Optional<SomeType<Int>> { ... }
extension Collection where Element == Optional<SomeType<Int8>> { ... }
extension Collection where Element == Optional<SomeType<UInt8>> { ... }
extension Collection where Element == Optional<SomeType<Int16>> { ... }
extension Collection where Element == Optional<SomeType<UInt16>> { ... }
extension Collection where Element == Optional<SomeType<Int32>> { ... }
extension Collection where Element == Optional<SomeType<UInt32>> { ... }
extension Collection where Element == Optional<SomeType<Int64>> { ... }
extension Collection where Element == Optional<SomeType<UInt64>> { ... }

编辑2018年6月23日:

解决方案#1-完全通用,但必须是功能而非计算属性

Ole's reply上展开:

优点:如果values()成为func而不是计算所得的属性,则这是一个很好的解决方案。

缺点:没有一种将这种方法实现为计算属性的方法,Swift的快速帮助弹出窗口在检查代码中的values()时显示[T]和[T?]。即:它只是说func values<T>() -> [T] where T : BinaryInteger,这不是非常有用的信息或Swifty。但是,当然它仍然是强类型的。

extension Collection {
    func values<T>() -> [T] where Element == SomeType<T> {
        return map( { $0.value })
    }

    func values<T>() -> [T?] where Element == SomeType<T>? {
        return map( { $0?.value })
    }
}

解决方案2-可选协议解决方法

Martin's reply上展开:

优点:允许使用计算属性(由于不需要func parens,最终用户可以使用更干净的属性),并在Xcode的“快速帮助”弹出窗口中显示推断的类型。

缺点:从内部代码的角度来看,这不是很优雅,因为它需要一种解决方法。但不一定是缺点。

// Define OptionalType

protocol OptionalType {
    associatedtype Wrapped
    var asOptional: Wrapped? { get }
}

extension Optional: OptionalType {
    var asOptional: Wrapped? {
        return self
    }
}

// Extend Collection

extension Collection where Element: SomeTypeProtocol {
    var values: [Element.NumberType] {
        return self.map( { $0.value })
    }
}

extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
    var values: [Element.Wrapped.NumberType?] {
        return self.map( { $0.asOptional?.value })
    }
}

2 个答案:

答案 0 :(得分:9)

Martin R's answer是一个很好的解决方案。另一种不需要额外标记协议的方法是:在Collection上写一个 unconstrained 扩展名,然后在该扩展名中定义一个 constrained 的泛型函数。到where Element == SomeType<T>?

extension Collection {
    func values<T>() -> [T?] where Element == SomeType<T>? {
        return map( { $0?.value })
    }
}

这有效:

let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
arr.values() // [Optional(123), Optional(456)]

您会注意到,我使用了func而不是计算属性。我无法正确理解通用语法。这不行吗?

extension Collection {
    // error: consecutive declarations on a line must be separated by ';'
    var values<T>: [T?] where Element == SomeType<T>? {
        return self.map( { $0?.value })
    }
}

答案 1 :(得分:4)

我不知道现在是否有一个更简单的解决方案,但是您可以使用与How can I write a function that will unwrap a generic property in swift assuming it is an optional type?Creating an extension to filter nils from an Array in Swift中相同的“技巧”,这个想法可以追溯到this Apple Forum Thread

首先定义所有可选选项都遵循的协议:

protocol OptionalType {
    associatedtype Wrapped
    var asOptional: Wrapped? { get }
}

extension Optional : OptionalType {  
    var asOptional: Wrapped? {  
        return self 
    }  
}  

现在所需的扩展名可以定义为

extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
    var values: [Element.Wrapped.NumberType?] {
        return self.map( { $0.asOptional?.value })
    }
}

并且可以按预期工作:

let arr = [SomeType(value: 123), nil, SomeType(value: 456)]
let v = arr.values

print(v) // [Optional(123), Optional(456)]
print(type(of: v)) // Array<Optional<Int>>