[swift] Generics et spécialisation

laurrislaurris Membre
janvier 2016 modifié dans Objective-C, Swift, C, C++ #1

Voilà  donc une classe Vehicule que je souhaite spécialiser en Vehicule<Voiture>.


 


Vehicule peut être initialisée avec un type explicite. Ici Voiture:



class Vehicule<VehiculeType> {
    var vehiculeType:VehiculeType.Type

    required init(type:VehiculeType.Type) {
        self.vehiculeType = type
    }
}

protocol Voiture {}

let voiture = Vehicule(type:Voiture.self)

--> Resultat dans un playground = Vehicule<Voiture>


 


 


Maintenant, et c'est là  ma question, je voudrais créer un type spécialisé, non pas en spécifiant explicitement le type, mais avec un initialisateur dédié comme par exemple:

 



let voiture5Portes = Vehicule(voitureAvecPortes:5)


 

Je modifie donc mon code comme suit:

 



protocol Voiture {}

class Vehicule<VehiculeType> {
    var vehiculeType:VehiculeType.Type
    var nombreDePortes:Int?

    required init(type:VehiculeType.Type) {
        self.vehiculeType = type
    }

    convenience init(voitureAvecPortes nbportes:Int) {
        self.init(type:Voiture.self as! VehiculeType.Type)
        self.nombreDePortes = nbportes
    }
}

let cinqPortes = Vehicule(voitureAvecPortes: 5)

D'abord, j'ai été obligé de caster Voiture.self en Vehicule.Type pour que le compiler me laisse tranquille, alors que dans la version précédente, je n'en avais pas besoin.


 


Ensuite, le compiler coince  quand j'essaie de créer la voiture à  la dernière ligne:


Generic Parameter 'VehiculeType' could not be inferred


 


Pourquoi donc alors que le VehiculeType est spécifié explicitement comme étant une Voiture ?!


 


Merci pour vos lumières. 


Réponses

  • Pourquoi tu voudrais faire une chose pareille ?


    La généricité ne te suffit pas ? Tu essaie de faire quoi ?


  • FKDEVFKDEV Membre
    janvier 2016 modifié #3

    Pourquoi donc alors que le VehiculeType est spécifié explicitement comme étant une Voiture ?!


    Ce n'est sans doute pas assez explicite, il faut aller voir dans la deuxième méthode init pour trouver un endroit où le paramètre de la classe est utilisé.

    Je ne sais pas quelles sont les règles qui permettent au compilateur d'inférer le type d'une classes générique, mais je peux comprendre que ton deuxième exemple est trop complexe à  analyser pour le compilateur.
    A priori je dirai que l'inférence ne fonctionne que si le paramètre de la classe est présent dans les paramètres du constructeur.
  • laurrislaurris Membre
    janvier 2016 modifié #4

    Certaines API existantes fonctionnent comme cela ( sans préjuger de l'implementation).


     


    Par exemple, NSExpression dans Foundation. On peut initialiser une expression comme ceci:



    let constantExpression = NSExpresssion(forConstantValue:2) //  expressionType == .ConstantValueExpression

    Ou bien:



    let evaluatedExpression = NSExpression.forEvaluatedObject() // expresionType == .evaluatedObjectExpression

    (dans le dernier cas c'est une class function mais cela ne change rien au pb).


     


    Ces deux instances partagent une même fonction expressionValueWithObject(object:Any?, context:Dictionary) -> Any? mais les signatures diffèrent selon le type d'expression:


    Une ConstantValueExpression renverra toujours la constante avec laquelle on l'a initialisée:



    var constantValue:T
    func expressionValueWithObject(object:Any?, context:Dictionary) -> T

    alors qu'une expression EvaluatedObjectExpression renverra l'objet évalué:



    func expressionValueWithObject(object:T, context:Dictionary) -> T

    Mon idée était de créer des versions spécialisées à  l'initialisation puis d'implémenter -evaluateWithObject dans des protocol extensions ciblés avec une where clause.


     


    Donc pour répondre à  la question, voilà  ce que j'essaie de faire : NSExpression en swift avec des génériques.


    Pour info: en Objective-C NSExpression est une classe abstraite (class cluster). Ce serait un bon exemple d'adaptation d'un class cluster en swift, sachant que swift privilégie les initializers aux class functions.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Normalement, avec les Generics, on ferait :



    class Vehicule<VehiculeType>
    {
    }

    protocol Voiture {}

    let voiture = Vehicule<Voiture>()

    Ou, peut-être :



    class Vehicule<VehiculeType>
    {

    }

    protocol Voiture
    {
    var nombreDePortes: Int { get }

    init(nombreDePortes: Int)
    }

    class Renault : Vehicule<Renault>, Voiture
    {
    let nombreDePortes: Int

    required init(nombreDePortes: Int)
    {
    self.nombreDePortes = nombreDePortes
    }
    }

    let cinqPortes: Voiture = Renault(nombreDePortes: 5)

    Mais je crois que ce serait relativement inutile.


  • D'autant qu'en regardant au code de NSExpression tu n'as aucunement besoin de faire une telle chose...


    Tu maniple des NSArray, des NSNumber et des NSString, un enum et on est bon. Je pense que tu essaie de trop te compliquer la vie sur le coup là.


     


    Et je ne vois pas trop en quoi NSExpression serait une classe abstraite. (Je suis prêt à  te croire hein mais a priori pour moi ça l'est pas).


  • laurrislaurris Membre
    janvier 2016 modifié #7

    Juré, NSExpression est une classe abstraite en Objective-C. Quand on fait [NSExpression expressionForConstantValue:@o], on n'obtient pas une NSExpression mais une sous-classe.


     


    Cela étant, je suis d'accord avec toi pour dire qu'on peut implementer NSExpression sans generics. D'ailleurs je l'ai fait.


     


    Mais là  je voudrais bénéficier du typage fort de swift.


    Le fait de connaitre à  l'avance le type du résultat de l'évaluation est un gros avantage par rapport à  l'Objective-C en terme de type checking et de performance.


    Par exemple, en swift pur, quand on fait NSExpression(forConstantValue:1).expressionValueWithObject(nil, context:nil), le type checkeur devrait supposer qu'on obtient un Int.


    Ou alors NSExpression(forConstantValue:1).variable devrait donner une erreur de compilation, ce qui n'est pas le cas en OC.


    Et comme les NSPredicate sont composés de NSExpression comparées au moment de l'évaluation, le type checkeur devrait aussi refuser des expressions non comparables. Il serait impossible de construire un predicate "o" < 1 alors qu'en OC c'est possible et ça bogue plus tard.


     


    Donc je pense que ça vaudrait le coup de pousser Swift dans ses retranchements pour améliorer l'implementation OC. Et aussi pour mieux connaitre Swift et toute sa bande de (sales?) types.


  • J'ai essayé ça



    protocol ExpressionTypeProtocol {}

    struct Constant : ExpressionTypeProtocol {
        let value:Any?
    }

    protocol NSExpressionProtocol {
        typealias ExpressionType
        typealias ConstantType
        
        var expressionType:ExpressionType {get set}
        init(type:ExpressionType)
        init(forConstantValue v:ConstantType)
    }

    final class NSExpression : NSExpressionProtocol {
        var expressionType:ExpressionTypeProtocol
        
        init(type t:ExpressionTypeProtocol) {
            self.expressionType = t
        }
        
        convenience init(forConstantValue v:Any?){
            self.init(type:Constant(value: v))
        }
    }

    extension NSExpressionProtocol where Self.ExpressionType == Constant {
        func expressionValueWithObject(object:Any?, context:[String:NSExpression]?) -> Self.ConstantType {
            return self.constantValue
        }
        
        var constantValue : ConstantType {
            return self.expressionType.value as! ConstantType
        }
    }

    let e = NSExpression(forConstantValue:"o").expressionValueWithObject(nil,context:nil)

    Et maintenant Monsieur Compilo me dit String is not convertible to Any?


     


    Il est dingue ce type !


  • Hello,


     


    Je pense que ça vient de ton extension sur NSExpressionProtocol et la contrainte que tu lui appliques. 



    extension NSExpressionProtocol where Self.ExpressionType == Constant

    Ici le type génériques ExpressionType doit être égale à  Constant mais dans ton code il est inféré a ExpressionTypeProtocol. 


    Même si Constant se conforme a ExpressionProtocol ça reste deux types différents. 

Connectez-vous ou Inscrivez-vous pour répondre.