简体   繁体   English

Swift 中构造泛型类型的扩展

[英]Extension of constructed generic type in Swift

Is it possible to extend an generic class for a specialised/constructed generic type?是否可以为特殊/构造的泛型类型扩展泛型类? I would like to extend Int Arrays with a method to calculate the sum of its elements.我想用一种方法来扩展 Int Arrays 来计算其元素的总和。

eg例如

extension Array<Int> {

    func sum() -> Int {
        return reduce(0) { $0 + $1 }
    }

}

This can be achieved using protocol extensions (See The Swift Programming Language: Protocols for more information).这可以使用协议扩展来实现(有关更多信息,请参阅Swift 编程语言:协议)。 In Swift 3:在 Swift 3 中:

To sum just Int s you could do:总结只是Int你可以这样做:

extension Sequence where Iterator.Element == Int {
    var sum: Int {
        return reduce(0, +)
    }
}

Usage:用法:

let nums = [1, 2, 3, 4]
print(nums.sum) // Prints: "10"

Or, for something more generic you could what @Wes Campaigne suggested and create an Addable protocol:或者,对于更通用的内容,您可以按照@Wes Campaigne 的建议创建一个Addable协议:

protocol Addable {
    init()
    func + (lhs: Self, rhs: Self) -> Self
}

extension Int   : Addable {}
extension Double: Addable {}
extension String: Addable {}
...

Next, extend Sequence to add sequences of Addable elements:接着,扩展Sequence添加的序列Addable元素:

extension Sequence where Iterator.Element: Addable {
    var sum: Iterator.Element {
        return reduce(Iterator.Element(), +)
    }
}

Usage:用法:

let doubles = [1.0, 2.0, 3.0, 4.0]
print(doubles.sum) // Prints: "10.0"

let strings = ["a", "b", "c"]
print(strings.sum) // Prints: "abc"

Managed to get something working in an extensible, generic fashion without abusing the type system too badly, however it has some limitations.设法使某些东西以可扩展的通用方式工作,而不会严重滥用类型系统,但是它有一些限制。

protocol Addable {
    func +(lhs: Self, rhs: Self) -> Self
    class var identity: Self { get }
}

extension Int : Addable {
    static var identity: Int { get { return 0 } }
}

extension String : Addable {
    static var identity: String { get { return "" } }
}

extension Array {
    func sum<U : Addable>() -> U? {
        let s: U? = U.identity
        return self.sum(s)
    }

    func sum<U : Addable>(start: U?) -> U? {
        return reduce(start) { lhs, rhs in
            switch (lhs, rhs) {
            case (.Some(let left), let right as U):
                return left + right
            default:
                return nil
            }
        }
    }
}

Specifically: with this solution, type inferencing won't work on the no-parameter sum() method, so you have to either annotate the expected return type or give it a starting value (from which it can infer the type).具体来说:使用此解决方案,类型推断不适用于无参数sum()方法,因此您必须注释预期的返回类型或为其提供一个起始值(从中可以推断类型)。

Note also that this returns a value of Optional type: if for any reason a sum of the expected type cannot be computed from the array, it returns nil.还要注意,这会返回一个 Optional 类型的值:如果由于某种原因无法从数组中计算出预期类型的​​总和,则返回 nil。

To illustrate:为了显示:

let int_array = Array(1...10)

let x: Int? = int_array.sum() // result: {Some 55}
let x2 = int_array.sum(0) // result: {Some 55}
let x3 = int_array.sum() // Compiler error because it can't infer type


let string_array = ["a", "b", "c"]

let y: String? = string_array.sum() // result: {Some "abc"}
let y2 = string_array.sum("") // result: {Some "abc"}

let y3: Int? = string_array.sum() // result: nil  (can't cast String to Int)
let y4 = string_array.sum(0) // result: nil  (can't cast String to Int)


let double_array = [1.3, 4.2, 2.1]

let z = double_array.sum(0.0) // Compiler error because we haven't extended Double to be Addable

Swift 5.x:斯威夫特 5.x:

extension Array where Element == Int {

    var sum: Int {
        reduce(0, +)
    }
}

Looks like you can't.好像不能The closest we can get is the function我们能得到的最接近的是函数

func sum(a:Array<Int>) -> Int {
    return a.reduce(0) {$0 + $1}
}

Swift will allow you to add extension on the Array class but not specifically to a specialized version of the class. Swift 将允许您在 Array 类上添加扩展,但不会专门添加到该类的专门版本。

error: <REPL>:108:1: error: non-nominal type 'Array<Int>' cannot be extended

You can extend the Array class.您可以扩展 Array 类。

extension Array {

    func sum() -> Int {
        return reduce(0) { $0 + $1 }
    }
}

The problem is now with the + operator现在的问题是+运算符

error: <REPL>:102:16: error: could not find an overload for '+' that accepts the supplied arguments
        return reduce(0) { $0 + $1 }

This is somewhat expected since we cannot be sure that the + operator will be will be overloaded for all the possible types that could be used in an array.这在一定程度上是预料之中的,因为我们不能确定+运算符将被重载用于数组中所有可能的类型。

So we could try to constraint the operation only on certain classes.所以我们可以尝试将操作限制在某些类上。 Something like就像是

class Dummy {
}

extension Array {
    func someFunc<T:Dummy>() -> Int {
       return 0
    }
}

var l = [Dummy()]
var r = l.someFunc() // Expect 0

Conceptually this should work (currently it seems that there is a bug, Xcode crashes when evaluating a playground using this code).从概念上讲,这应该可行(目前似乎存在一个错误,Xcode 在使用此代码评估游乐场时崩溃)。 In the eventually that it works, we cannot use this trick since the type Int is not a class.最后它起作用了,我们不能使用这个技巧,因为类型Int不是一个类。

extension Array {
    func sum<T:Int>() -> T {
        return reduce(0) { $0 + $1 }
    }
}

error: <REPL>:101:14: error: inheritance from non-protocol, non-class type 'Int'
    func sum<T:Int>() -> T {

I also looked at extending the Array class with a protocol but again Int not being a class makes it impossible.我还研究了使用协议扩展 Array 类,但再次Int不是一个类使得它不可能。 If the numeric types were classes, it would be nice if we could have a protocol to define that a class can be added just like Comparable or Equatable but my understanding is that protocol cannot define generic function which would be needed to create a Addable protocol.如果数字类型是类,那么如果我们可以有一个协议来定义一个类可以像ComparableEquatable一样添加,那就Equatable但我的理解是协议不能定义创建可Addable协议所需的通用函数。

Edit:编辑:

As stated by other answers, you can make it work for Int by explicitly checking and casting to Int in the closure.正如其他答案所述,您可以通过在闭包中显式检查并转换为 Int 来使其适用于 Int 。 I guess I missed it will investigating.我想我错过了它会调查。 But it would still be nice if we could have a generic way of working with numeric types.但是如果我们能有一种通用的方法来处理数字类型,那还是很好的。

It is possible to return a real sum-value after you have tested for the int-type in sum() .sum()测试 int 类型后,可以返回一个真实的 sum 值。 Doing so I would solve the problem as follows:这样做我将解决以下问题:

import Cocoa

extension Array {
    func sum() -> Int {
        if !(self[0] is Int) { return 0; }
        var sum = 0;
        for value in self { sum += value as Int }
        return sum;
    }
}

let array = [1,2,3,4,5]
array.sum() // =15

let otherArray = ["StringValue"]
otherArray.sum() // =0

Alexander,亚历山大,

Here's how you can do it:您可以这样做:

extension Array {
    func sum() -> Int {
        return reduce(0) { ($0 as Int) + ($1 as Int) }
    }
}

Works like a charm, tested in the playground.就像一个魅力,在操场上测试。 However, you might get into trouble if you call this function on different types of arrays.但是,如果您在不同类型的数组上调用此函数,您可能会遇到麻烦。

Swift 5.x斯威夫特 5.x

There is a built in protocol, AdditiveArithmetic , that defines the generic + operator, so we can write an extension that works for all 'Addable' types automatically.有一个内置协议AdditiveArithmetic ,它定义了泛型+运算符,因此我们可以编写一个自动适用于所有“可添加”类型的扩展。 This is the example given on Apple's documentation page:这是 Apple 文档页面上给出的示例:

extension Sequence where Element: AdditiveArithmetic {
    func sum() -> Element {
        return reduce(.zero, +)
    }
}

This will now work on any Sequence or Collection with any kind of 'Addable' elements, including Int , Double , Float etc.现在,这将适用于具有任何类型的“可添加”元素的任何SequenceCollection ,包括IntDoubleFloat等。

you can do it as well你也可以做到

extension Array {
    func sum () -> Int? {
        guard self.count > 0  && self.first is Int  else {
            return nil
        }
        var s = 0
        forEach {
            s += $0 as! Int
        }
        return s
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM