Расширение построенного общего типа в Swift
Можно ли расширить общий класс для специализированного/построенного общего типа? Я хотел бы расширить Int массивы методом для вычисления суммы его элементов.
например.
extension Array<Int> {
func sum() -> Int {
return reduce(0) { $0 + $1 }
}
}
Ответы
Ответ 1
Это может быть достигнуто с использованием расширений протокола (см. Язык быстрого программирования: протоколы для получения дополнительной информации). В Swift 3:
Суммируя только Int
, вы можете сделать:
extension Sequence where Iterator.Element == Int {
var sum: Int {
return reduce(0, +)
}
}
Использование:
let nums = [1, 2, 3, 4]
print(nums.sum) // Prints: "10"
Или, для чего-то более общего, вы могли бы предложить @Wes Campaigne и создать протокол Addable
:
protocol Addable {
init()
func + (lhs: Self, rhs: Self) -> Self
}
extension Int : Addable {}
extension Double: Addable {}
extension String: Addable {}
...
Затем добавьте Sequence
, чтобы добавить последовательности элементов Addable
:
extension Sequence where Iterator.Element: Addable {
var sum: Iterator.Element {
return reduce(Iterator.Element(), +)
}
}
Использование:
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"
Ответ 2
Удалось заставить что-то работать в расширяемом, универсальном стиле, не злоупотребляя системой типов слишком плохо, однако у него есть некоторые ограничения.
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
}
}
}
}
В частности: с помощью этого решения тип inferencing не будет работать с методом no-parameter sum()
, поэтому вам нужно либо аннотировать ожидаемый тип возвращаемого значения, либо дать ему начальное значение (из которого он может вывести тип).
Обратите внимание, что это возвращает значение Необязательного типа: если по какой-либо причине сумма ожидаемого типа не может быть вычислена из массива, она возвращает nil.
Чтобы проиллюстрировать:
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
Ответ 3
Похоже, вы не можете. Самое близкое, что мы можем получить, это функция
func sum(a:Array<Int>) -> Int {
return a.reduce(0) {$0 + $1}
}
Swift позволит вам добавить расширение в класс Array, но не специально для специализированной версии класса.
error: <REPL>:108:1: error: non-nominal type 'Array<Int>' cannot be extended
Вы можете расширить класс Array.
extension Array {
func sum() -> Int {
return reduce(0) { $0 + $1 }
}
}
Теперь проблема с оператором +
error: <REPL>:102:16: error: could not find an overload for '+' that accepts the supplied arguments
return reduce(0) { $0 + $1 }
Это несколько ожидаемо, так как мы не можем быть уверены, что оператор +
будет перегружен для всех возможных типов, которые могут быть использованы в массиве.
Таким образом, мы могли бы попытаться ограничить операцию только некоторыми классами. Что-то вроде
class Dummy {
}
extension Array {
func someFunc<T:Dummy>() -> Int {
return 0
}
}
var l = [Dummy()]
var r = l.someFunc() // Expect 0
Концептуально это должно работать (в настоящее время кажется, что есть ошибка, ошибка Xcode при оценке игровой площадки с использованием этого кода). В конечном счете, это работает, мы не можем использовать этот трюк, так как тип 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 {
Я также рассмотрел расширение класса Array протоколом, но снова Int
, не являющийся классом, делает невозможным. Если числовые типы были классами, было бы неплохо, если бы у нас мог быть протокол для определения того, что класс может быть добавлен так же, как Comparable
или Equatable
, но я понимаю, что протокол не может определить общую функцию, которая потребуется для создания a Addable
.
Edit:
Как указано в других ответах, вы можете заставить его работать для Int, явно проверяя и отбрасывая Int в закрытии. Думаю, я пропустил это, будет расследовать. Но было бы неплохо, если бы у нас был общий способ работы с числовыми типами.
Ответ 4
Можно вернуть реальное значение суммы после того, как вы протестировали int-type в sum()
. Поэтому я решил бы проблему следующим образом:
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
Ответ 5
Александр
Вот как вы можете это сделать:
extension Array {
func sum() -> Int {
return reduce(0) { ($0 as Int) + ($1 as Int) }
}
}
Работает как шарм, проверенный на детской площадке. Однако вы можете столкнуться с проблемами, если вы вызываете эту функцию на разных типах массивов.
Ответ 6
вы также можете сделать это
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
}
}