web-dev-qa-db-ja.com

Swiftの構築ジェネリック型の拡張

特殊化/構築されたジェネリック型のジェネリッククラスを拡張することは可能ですか?要素の合計を計算するメソッドでInt配列を拡張したいと思います。

例えば.

extension Array<Int> {

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

}
47

これは、プロトコル拡張機能を使用して実現できます(詳細については The Swift Programming Language:Protocols を参照してください)。In Swift 3:

単にIntsを合計するには、次のようにします。

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"
46
ABakerSmith

型システムを酷使することなく、拡張可能で一般的な方法で何かを動作させることができましたが、いくつかの制限があります。

_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
            }
        }
    }
}
_

具体的に:このソリューションでは、型推論はno-parameter sum()メソッドでは機能しないため、期待される戻り値の型に注釈を付けるか、開始値(型を推論できる値)を指定する必要があります)。

また、これはOptional型の値を返すことに注意してください:何らかの理由で期待される型の合計が配列から計算できない場合、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
_
8
Wes Campaigne

できないように見えます。最も近いものは関数です

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のようにクラスを追加できることを定義するプロトコルがあればいいのですが、私の理解では、プロトコルは汎用関数を定義できないAddableプロトコルを作成するために必要です。

編集:

他の回答で述べたように、クロージャーで明示的にチェックしてIntにキャストすることで、Intで動作させることができます。私はそれが調査するのを逃したと思います。しかし、数値型を操作する一般的な方法があれば、それでもいいでしょう。

3
Rod

アレクサンダー、

方法は次のとおりです。

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

遊び場でテストされたチャームのように機能します。ただし、この関数を異なるタイプの配列で呼び出すと、問題が発生する可能性があります。

2
Jean Le Moignan

sum()でint-typeをテストした後、実際の合計値を返すことができます。そうすることで、次のように問題を解決できます。

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
1
wottpal