web-dev-qa-db-fra.com

Extension du type générique construit dans Swift

Est-il possible d'étendre une classe générique pour un type générique spécialisé/construit? Je voudrais étendre Int Arrays avec une méthode pour calculer la somme de ses éléments.

par exemple.

extension Array<Int> {

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

}
47

Ceci peut être réalisé en utilisant des extensions de protocole (Voir Le Swift Langage de programmation: Protocoles pour plus d'informations). Dans Swift 3:

Pour résumer juste Ints, vous pouvez faire:

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

Usage:

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

Ou, pour quelque chose de plus générique, vous pourriez ce que @Wes Campaigne a suggéré et créer un protocole Addable:

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

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

Ensuite, étendez Sequence pour ajouter des séquences d'éléments 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"
46
ABakerSmith

Géré pour que quelque chose fonctionne de manière extensible et générique sans trop abuser du système de type, mais il a certaines limites.

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

Plus précisément: avec cette solution, l'inférence de type ne fonctionnera pas avec la méthode no-parameter sum(), vous devez donc soit annoter le type de retour attendu, soit lui donner une valeur de départ (à partir de laquelle il peut déduire le type ).

Notez également que cela renvoie une valeur de type Facultatif: si pour une raison quelconque, une somme du type attendu ne peut pas être calculée à partir du tableau, elle retourne nil.

Pour illustrer:

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

On dirait que tu ne peux pas. Le plus proche que nous pouvons obtenir est la fonction

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

Swift vous permettra d'ajouter une extension à la classe Array mais pas spécifiquement à une version spécialisée de la classe.

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

Vous pouvez étendre la classe Array.

extension Array {

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

Le problème est maintenant avec le + opérateur

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

Ceci est quelque peu attendu car nous ne pouvons pas être sûrs que le + L'opérateur sera surchargé pour tous les types possibles qui pourraient être utilisés dans un tableau.

On pourrait donc essayer de contraindre l'opération uniquement sur certaines classes. Quelque chose comme

class Dummy {
}

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

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

Conceptuellement, cela devrait fonctionner (actuellement, il semble qu'il y ait un bogue, Xcode se bloque lors de l'évaluation d'une aire de jeux à l'aide de ce code). En fin de compte que cela fonctionne, nous ne pouvons pas utiliser cette astuce car le type Int n'est pas une classe.

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 {

J'ai également cherché à étendre la classe Array avec un protocole, mais encore une fois, Int ne pas être une classe rend cela impossible. Si les types numériques étaient des classes, ce serait bien si nous pouvions avoir un protocole pour définir qu'une classe peut être ajoutée comme Comparable ou Equatable mais ma compréhension est que le protocole ne peut pas définir de fonction générique qui serait nécessaire pour créer un protocole Addable.

Modifier:

Comme indiqué par d'autres réponses, vous pouvez le faire fonctionner pour Int en vérifiant explicitement et en le castant dans Int dans la fermeture. Je suppose que j'ai raté l'enquête. Mais ce serait quand même bien si nous pouvions avoir une façon générique de travailler avec les types numériques.

3
Rod

Alexandre,

Voici comment procéder:

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

Fonctionne comme un charme, testé dans la cour de récréation. Cependant, vous pourriez avoir des problèmes si vous appelez cette fonction sur différents types de tableaux.

2
Jean Le Moignan

Il est possible de renvoyer une valeur de somme réelle après avoir testé le type int dans sum(). Ce faisant, je résoudrais le problème comme suit:

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