我们如何创建一个在Swift中对Number类型求和的通用Array Extension?

myt*_*thz 22 swift

Swift允许您创建一个数组扩展,它将Integer与:

extension Array {
    func sum() -> Int {
        return self.map { $0 as Int }.reduce(0) { $0 + $1 }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在可以用来总结Int[]如下:

[1,2,3].sum() //6
Run Code Online (Sandbox Code Playgroud)

但是我们如何制作一个支持对其他数字类型求和的通用版本Double[]呢?

[1.1,2.1,3.1].sum() //fails
Run Code Online (Sandbox Code Playgroud)

这个问题不是如何对数字求和,而是如何创建一个通用的数组扩展来实现它.


越来越近

如果能帮助任何人更接近解决方案,这是我能够获得的最接近的:

您可以创建一个可以满足我们需要的协议,即:

protocol Addable {
    func +(lhs: Self, rhs: Self) -> Self
    init()
}
Run Code Online (Sandbox Code Playgroud)

然后扩展我们想要支持的每种类型,以符合上述协议:

extension Int : Addable {
}

extension Double : Addable {
}
Run Code Online (Sandbox Code Playgroud)

然后添加具有该约束的扩展:

extension Array {
    func sum<T : Addable>(min:T) -> T
    {
        return self.map { $0 as T }.reduce(min) { $0 + $1 }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在可以用来对付我们扩展为支持协议的数字,即:

[1,2,3].sum(0) //6
[1.1,2.1,3.1].sum(0.0) //6.3
Run Code Online (Sandbox Code Playgroud)

不幸的是,我无法在不提供参数的情况下使其工作,即:

func sum<T : Addable>(x:T...) -> T?
{
    return self.map { $0 as T }.reduce(T()) { $0 + $1 }
}
Run Code Online (Sandbox Code Playgroud)

修改后的方法仍然适用于1个参数:

[1,2,3].sum(0) //6
Run Code Online (Sandbox Code Playgroud)

但是在没有参数的情况下调用它时无法解析方法,即:

[1,2,3].sum() //Could not find member 'sum'
Run Code Online (Sandbox Code Playgroud)

添加Integer到方法签名也无法帮助解决方法:

func sum<T where T : Integer, T: Addable>() -> T?
{
    return self.map { $0 as T }.reduce(T()) { $0 + $1 }
}
Run Code Online (Sandbox Code Playgroud)

但希望这将有助于其他人更接近解决方案.


一些进展

从@GabrielePetronella回答,如果我们在调用网站上明确指定类型,我们可以调用上面的方法,如:

let i:Int = [1,2,3].sum()
let d:Double = [1.1,2.2,3.3].sum()
Run Code Online (Sandbox Code Playgroud)

Gab*_*lla 8

我想我找到了一种合理的方法,从scalaz中借鉴一些想法并从你提议的实现开始.基本上我们想要的是具有代表幺半群的类型类.

换句话说,我们需要:

  • 关联函数
  • 身份值(即零)

这是一个建议的解决方案,它围绕快速类型系统限制

首先,我们友好的Addable类型类

protocol Addable {
    class func add(lhs: Self, _ rhs: Self) -> Self
    class func zero() -> Self
}
Run Code Online (Sandbox Code Playgroud)

现在让我们来Int实现吧.

extension Int: Addable {
    static func add(lhs: Int, _ rhs: Int) -> Int {
        return lhs + rhs
    }

    static func zero() -> Int {
        return 0
    }
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.现在我们已经拥有了构建通用`sum函数所需的所有部分:

extension Array {
    func sum<T : Addable>() -> T {
        return self.map { $0 as T }.reduce(T.zero()) { T.add($0, $1) }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们来试试吧

let result: Int = [1,2,3].sum() // 6, yay!
Run Code Online (Sandbox Code Playgroud)

由于类型系统的限制,您需要显式地转换结果类型,因为编译器无法自行Addable解析解析Int.

所以你不能只做:

let result = [1,2,3].sum()
Run Code Online (Sandbox Code Playgroud)

我认为这种方法是一个可以忍受的缺点.

当然,这是完全通用的,它可以用于任何类,任何类型的monoid.我之所以没有使用默认+运算符,而是我定义了一个add函数,是因为它允许任何类型实现Addable类型类.如果你使用+,那么一个没有+运算符定义的类型,那么你需要在全局范围内实现这样的运算符,我有点不喜欢.

无论如何,如果你需要例如同时生成' IntString'multipliable',它的工作原理*是这样的,因为它是Int为`String而定义的.

protocol Multipliable {
    func *(lhs: Self, rhs: Self) -> Self
    class func m_zero() -> Self
}

func *(lhs: String, rhs: String) -> String {
    return rhs + lhs
}
extension String: Multipliable {
    static func m_zero() -> String {
        return ""
    }
}
extension Int: Multipliable {
    static func m_zero() -> Int {
        return 1
    }
}

extension Array {
    func mult<T: Multipliable>() -> T {
        return self.map { $0 as T }.reduce(T.m_zero()) { $0 * $1 }
    }
}

let y: String = ["hello", " ", "world"].mult()
Run Code Online (Sandbox Code Playgroud)

现在数组String可以使用该方法mult执行反向连接(只是一个愚蠢的例子),并且实现使用*新定义的运算符String,而Int保持使用其常用*运算符,我们只需要为monoid定义零.

对于代码清洁,我更喜欢将整个类型类实现放在extension范围内,但我想这是一个品味问题.


ABa*_*ith 7

从Swift 2开始,可以使用协议扩展来完成此操作.(有关更多信息,请参阅Swift编程语言:协议).

首先,Addable协议:

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

extension Int   : Addable {}
extension Double: Addable {}
// ...
Run Code Online (Sandbox Code Playgroud)

接下来,扩展SequenceType以添加Addable元素序列:

extension SequenceType where Generator.Element: Addable {
    var sum: Generator.Element {
        return reduce(0, combine: +)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

let ints = [0, 1, 2, 3]
print(ints.sum) // Prints: "6"

let doubles = [0.0, 1.0, 2.0, 3.0]
print(doubles.sum) // Prints: "6.0"
Run Code Online (Sandbox Code Playgroud)