From generic protocol to type !

JérémyJérémy Membre
février 2017 modifié dans Objective-C, Swift, C, C++ #1

Bonjour à  tous,


 


Je souhaiterais faire d'un protocol générique, un type à  part entière. Plutôt que de rentrer dans de longues explications, voici un code que j'ai inventé pour illustrer ma question.  :)



public protocol Generic {
associatedtype T

var x: Int { get set }
var fx: Double { get }

func gettingClass()-> T
}

// TataProtocol hérite de Generique pour être certain que toute implémentation de protocol
// répondra au contrat Generique (idem pour TitiProtocol)
public protocol TataProtocol: Generic {
func sayedHello()-> String
}

public protocol TitiProtocol: Generic {
func sayedGoodBye()-> String
}

public class Gen<Toto>: Generic {
public typealias T = Toto

private var _x: Int = 0
public var x: Int {
get {
return _x
}
set {
if newValue != _x {
_fx = nil
_x = newValue
}
}
}

private var _fx: Double?
public var fx: Double {
// On imagine ici un traitement lourd, pour des raisons d'optimisation on garde
// en mémoire le résultat du calcul
if _fx == nil {
_fx = cos(pow(Double(x) - 7.0, 3) * M_PI / 180.0)
}
return _fx!
}

public func gettingClass() -> Toto {
return Gen<Toto>() as! Toto
}
}

public class Tata: Gen<Tata>, TataProtocol {
public func sayedHello()-> String {
return "Hello Tata!"
}
}

public class MyTata: Gen<Tata>, TataProtocol {
public func sayedHello()-> String {
return "Hello MyTata!"
}
}

public class Titi: Gen<Titi>, TitiProtocol {
public func sayedGoodBye()-> String {
return "Bye Titi!!!"
}
}

let tata: Tata = Tata() // Ici pas de problème
tata.sayedHello()
tata.x = 19
print(tata.fx)

let y: TataProtocol = Tata() // Erreur : Protocol 'FirstProtocol' can only be
// used as a generic constraint because
// it has Self or associated type requirements

Auriez vous une petite idée ?  ::)


Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur

    Oui. Tout simplement, on ne peut pas faire comme ça  ::)


     


    À part d'un exercice, qu'est-ce que tu voudrais faire avec tels protocoles / classes ?


  • JérémyJérémy Membre
    février 2017 modifié #3


    À part d'un exercice, qu'est-ce que tu voudrais faire avec tels protocoles / classes ?




     


    J'aimerais qu'un protocol B oblige à  implémenter un protocol A. Pour éviter du code redondant, j'ai voulu passer par une classe générique... Pourquoi utiliser le protocol B comme typage ? Pour que toutes les implémentations de B puissent être utilisées.


     




    Oui. Tout simplement, on ne peut pas faire comme ça  ::)




     


    ça j'avais cru comprendre puisque ça ne fonctionne pas. ::)


    Tu me conseilles quoi ? De créer une extension de mon protocol A ("Generic" dans mon exemple) dans lequel je mettrai mon code commun ?


  • Joanna CarterJoanna Carter Membre, Modérateur


    J'aimerais qu'un protocol B oblige à  implémenter un protocol A. Pour éviter du code redondant, j'ai voulu passer par une classe générique... Pourquoi utiliser le protocol B comme typage ? Pour que toutes les implémentations de B puissent être utilisées.




     


    OK. Mais pourquoi l'associatedtype et qu'est-ce que tu comptes faire avec gettingClass() ?


     


     




    ça j'avais cru comprendre puisque ça ne fonctionne pas. ::)


    Tu me conseilles quoi ? De créer une extension de mon protocol A ("Generic" dans mon exemple) dans lequel je mettrai mon code commun ?




     


     


    Il faut expliquer plus de ce que tu veuilles faire

  • Je vais créer dans la soirée un exemple un peu plus parlant pour illustrer ma demande (qui relève plus de l'exercice que d'un réel cas que j'ai dans un projet). :)


  • Tu ne peux pas faire ta dernière exécution parce que ton protocol dispose d'un type générique que tu ne fournis pas à  l'initialisation. Ce que tu essayes de faire n'est pas permis, du moins actuellement.


     


    Tu peux le faire passer dans une fonction qui prend en générique ton protocol, c'est ce qu'ils expliquent.



    func myFunc<T: MyProtocol>(myProtocol: T)
  • JérémyJérémy Membre
    février 2017 modifié #7

    Je viens de faire un autre petit exemple à  Joanna qui sera (je l'espère) un peu plus parlant pour elle :


     



    public protocol GenericFormProtocol {
    associatedtype T

    func growingSize(by: Double)-> T
    func displayOnScreen(_ x: Int, _ y: Int)
    }

    public protocol SquareProtocol: GenericFormProtocol {
    var side: Double { get }
    var perimeter: Double { get }
    }

    public protocol RectangleProtocol: GenericFormProtocol {
    var width: Double { get }
    var height: Double { get }
    var perimeter: Double { get }
    }

    public class GenericForm<Type>: GenericFormProtocol {
    public typealias T = Type

    public func growingSize(by: Double) -> Type {
    return GenericForm<Type>() as! Type
    }

    public func displayOnScreen(_ x: Int, _ y: Int) {
    //Code
    }
    }

    public class Square: GenericForm<Square>, SquareProtocol {
    public let side: Double
    public var perimeter: Double {
    return side * 4.0
    }

    override public init() {
    side = 0
    }

    public init(side: Double) {
    self.side = side
    }
    }

    public class Rectangle: GenericForm<Rectangle>, RectangleProtocol {
    public let height: Double
    public let width: Double

    public var perimeter: Double {
    return height * 2.0 + width * 2.0
    }

    override public init() {
    height = 0.0
    width = 0.0

    }

    public init(height: Double, width: Double) {
    self.height = height
    self.width = width
    }
    }

    let rectangle: RectangleProtocol = Rectangle(height: 10, width: 23)
    // Erreur : Protocol 'FirstProtocol' can only be
    // used as a generic constraint because
    // it has Self or associated type requirements

    Mieux Joanna ?  :)




     


    Tu ne peux pas faire ta dernière exécution parce que ton protocol dispose d'un type générique que tu ne fournis pas à  l'initialisation. Ce que tu essayes de faire n'est pas permis, du moins actuellement.


     


    Tu peux le faire passer dans une fonction qui prend en générique ton protocol, c'est ce qu'ils expliquent.



    func myFunc<T: MyProtocol>(myProtocol: T)



     


    En gros si je comprends bien, tu ne peux pas utiliser un protocol comme type de déclaration d'une variable du moment que ce dernier dispose d'un type générique ?  ???


     


    Je vois pas la valeur ajoutée d'une telle contrainte.  :)


    Il est tout de même mieux d'utiliser les protocol comme type pour déclarer des variables...  ::)


  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #8

    Tu as beaucoup à  apprendre  :-*


     


    Ce que tu as fait, c'est bien compliqué.


     


    Pour le simplifier un peu :



    protocol GenericFormProtocol
    {
    func growingSize(by factor: Double) -> Self

    func displayOnScreen(_ x: Int, _ y: Int)
    }

    extension GenericFormProtocol
    {
    func displayOnScreen(_ x: Int, _ y: Int)
    {
    // code
    }
    }


    protocol SquareProtocol : GenericFormProtocol
    {
    var side: Double { get }

    var perimeter: Double { get }
    }


    protocol RectangleProtocol : GenericFormProtocol
    {
    var width: Double { get }

    var height: Double { get }

    var perimeter: Double { get }
    }


    struct Square : SquareProtocol
    {
    public let side: Double

    public var perimeter: Double
    {
    return side * 4.0
    }

    init()
    {
    self.init(side: 0.0)
    }

    init(side: Double)
    {
    self.side = side
    }

    func growingSize(by factor: Double) -> Square
    {
    return Square(side: self.side * factor)
    }
    }


    struct Rectangle : RectangleProtocol
    {
    public let height: Double

    public let width: Double

    public var perimeter: Double
    {
    return (height + width) * 2.0
    }

    init()
    {
    self.init(height: 0.0, width: 0.0)
    }

    init(height: Double, width: Double)
    {
    self.height = height

    self.width = width
    }

    func growingSize(by factor: Double) -> Rectangle
    {
    return Rectangle(height: self.height * factor, width: self.width * factor)
    }
    }

    Code de test



    {
    let s = Square(side: 10.0)

    let r = Rectangle(height: 10.0, width: 23.0)

    let shapes: [GenericFormProtocol] = [s, r]

    var grownShapes = [GenericFormProtocol]()

    for shape in shapes
    {
    let grownShape = shape.growingSize(by: 2.0)

    grownShapes.append(grownShape)
    }

    print(shapes)

    print(grownShapes)
    }

  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #9

    Et, j'oserais dire que l'on puisse oublier SquareProtocol et RectangleProtocol

     



    protocol GenericFormGenericFormProtocolProtocol
    {
    func growingSize(by factor: Double) -> Self

    func displayOnScreen(_ x: Int, _ y: Int)
    }

    extension GenericFormProtocol
    {
    func displayOnScreen(_ x: Int, _ y: Int)
    {
    // code
    }
    }


    struct Square : GenericFormProtocol
    {
    public let side: Double

    public var perimeter: Double
    {
    return side * 4.0
    }

    init()
    {
    self.init(side: 0.0)
    }

    init(side: Double)
    {
    self.side = side
    }

    func growingSize(by factor: Double) -> Square
    {
    return Square(side: self.side * factor)
    }
    }


    struct Rectangle : GenericFormProtocol
    {
    public let height: Double

    public let width: Double

    public var perimeter: Double
    {
    return (height + width) * 2.0
    }

    init()
    {
    self.init(height: 0.0, width: 0.0)
    }

    init(height: Double, width: Double)
    {
    self.height = height

    self.width = width
    }

    func growingSize(by factor: Double) -> Rectangle
    {
    return Rectangle(height: self.height * factor, width: self.width * factor)
    }
    }


  • Tu as beaucoup à  apprendre  :-*




     


    Le melon qu'elle a celle la ! xd


     




     


    Ce que tu as fait, c'est bien compliqué.




     


    J'avoue que ta solution est plus propre et fatalement moins complexe. o:)


     




    Et, j'oserais dire que l'on puisse oublier SquareProtocol et RectangleProtocol




     


    Oui... Mais non ! Pourquoi ?


     


    Pour déclarer une variable (exemple, quand tu passes un paramètre dans une méthode), je préfère utiliser le protocol pour être certain que toutes ses implémentations puissent y être éligible.


     


    Je trouve préférable d'utiliser :



    func myFunc(rectangle: RectangleProtocol)

    Que :



    func myFunc(rectangle: Rectangle)
  • Je crois qu'il y a déjà  eu un fil sur un sujet similaire.
    Je propose l'explication suivante (merci de me contredire si je me trompe car je ne suis pas un pro de la compilation swift) :


    Pour l'instant, on ne peut pas faire ça :
    public protocol Generic {
    associatedtype T

    func gettingClass()-> T
    }
    C'est à  cause du côté très statique et typé de swift.
    A chaque fois que tu vas créer une struct ou une class dérivant de Generic, il faut imaginer que swift va créer une nouvelle "instance" du protocol Generic en remplaçant T par le type utilisé.
    Donc si tu fais un Generic avec T = String et un autre avec T = Int, tu vas te retrouver avec deux protocoles incompatibles puisque l'un a une fonction qui renvoie un String et l'autre la même fonction qui renvoie un Int.
    Comment une struct pourrait dériver de ces deux protocoles à  la fois ?

    via http://stackoverflow.com/questions/27404137/swift-types-returning-constrained-generics-from-functions-and-methods

    Cela pourrait surement fonctionner avec un langage vraiment dynamique pour qui Generic resterait un seul protocol paramétrable au moment du runtime.
  • JérémyJérémy Membre
    février 2017 modifié #12


    Pour l'instant, on ne peut pas faire ça :



    public protocol Generic {
    associatedtype T

    func gettingClass()-> T
    }

    C'est à  cause du côté très statique et typé de swift.




     


    Si si si, tu peux. En revanche, tu ne peux le définir comme un type à  part entière (il en sera de même pour les protocols qui en hériteront) :

     



    let myGenericSystem: Generic




    A chaque fois que tu vas créer une struct ou une class dérivant de Generic, il faut imaginer que swift va créer une nouvelle "instance" du protocol Generic en remplaçant T par le type utilisé.

    Donc si tu fais un Generic avec T = String et un autre avec T = Int, tu vas te retrouver avec deux protocoles incompatibles puisque l'un a une fonction qui renvoie un String et l'autre la même fonction qui renvoie un Int.




     


    Oui et non. Les instances sont créées à  partir de l'implémentation (struc ou class) et non du protocol. Le protocol a la foncion de définir un contrat. Faut voir le truc dans le sens "si tu signes avec moi (si tu veux m'inplémenter), tu dois pouvoir gérer ça ça et ça et faire ceci et cela". Ceci dit, je comprends l'idée de ta phrase. ;)


     


    Dans l'absolue, si tu n'utilises, pas dans ta façon de coder, les protocols comme typage, tu n'auras pas de soucis. :)


     


    Mais c'est sur ce point précis que la bât blesse sur le langage (qui se dit orienté protocol)... Pourquoi ne pas pouvoir utiliser Generic comme type ? Le contrat est clair, "gettingClass()-> T = retourne moi la classe dont tu auras customisé le type". Du coup, que ce soit du Int ou de String on s'en fout complètement... :(


  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #13


     


    Le melon qu'elle a celle la ! xd




     


    :D   :D   :D


     




     


    Oui... Mais non ! Pourquoi ?


     


    Pour déclarer une variable (exemple, quand tu passes un paramètre dans une méthode), je préfère utiliser le protocol pour être certain que toutes ses implémentations puissent y être éligible.


     


    Je trouve préférable d'utiliser :



    func myFunc(rectangle: RectangleProtocol)

    Que :



    func myFunc(rectangle: Rectangle)



     


     


    Et je suis d'accord sur ça. Du coup, et étant donné que Square n'est qu'un Rectangle avec width et height qui sont égaux, je propose encore un petit changement :



    protocol GenericFormProtocol
    {
    func growingSize(by factor: Double) -> Self

    func displayOnScreen(_ x: Int, _ y: Int)
    }

    extension GenericFormProtocol
    {
    func displayOnScreen(_ x: Int, _ y: Int)
    {
    // code
    }
    }

    protocol RectangleProtocol : GenericFormProtocol
    {
    var height: Double { get }

    var width: Double { get }

    var perimeter: Double { get }
    }


    extension RectangleProtocol
    {
    var perimeter: Double
    {
    return (width + height) * 2.0
    }
    }


    struct Square : RectangleProtocol
    {
    public let height: Double

    public let width: Double

    init()
    {
    self.init(side: 0.0)
    }

    init(side: Double)
    {
    self.width = side

    self.height = side
    }

    func growingSize(by factor: Double) -> Square
    {
    return Square(side: self.width * factor)
    }
    }


    struct Rectangle : GenericFormProtocol
    {
    public let height: Double

    public let width: Double

    init()
    {
    self.init(height: 0.0, width: 0.0)
    }

    init(height: Double, width: Double)
    {
    self.height = height

    self.width = width
    }

    func growingSize(by factor: Double) -> Rectangle
    {
    return Rectangle(height: self.height * factor, width: self.width * factor)
    }
    }

    ça te plaà®t mieux ?  ???


  • JérémyJérémy Membre
    février 2017 modifié #14


    ça te plaà®t mieux ?  ???




     


    Non. ;D


     


    Pourquoi un carré qui est un rectangle particulier n'a t'il pas son propre protocol ?


     


    Exemple :



    protocol SquareProtocol: RectangleProtocol {
    var side: Double { get }
    }

    struct Square : SquareProtocol {
    public let height: Double
    public let width: Double
    public var side: Double { return width }

    init() {
    self.init(side: 0.0)
    }

    init(side: Double) {
    self.width = side
    self.height = side
    }

    func growingSize(by factor: Double) -> Square {
    return Square(side: self.side * factor)
    }
    }

  • Joanna CarterJoanna Carter Membre, Modérateur


     


    Non. ;D


     


    Pourquoi un carré qui est un rectangle particulier n'a t'il pas son propre protocol ?


     


    Exemple :



    protocol SquareProtocol: RectangleProtocol {
    var side: Double { get }
    }



     


    Oui, non, peut-être. Mais, avec ça, on aurait, apparemment, trois vars : height, width et side ; on pourrait conjecturer que c'est un objet en trois dimensions, non ?


     


    8--)

  • JérémyJérémy Membre
    février 2017 modifié #16


    Oui, non, peut-être. Mais, avec ça, on aurait, apparemment, trois vars : height, width et side ; on pourrait conjecturer que c'est un objet en trois dimensions, non ?


     


    8--)




     


    Elle est difficile à  satisfaire ! :'(


     


    Contre proposition :



    protocol SquareProtocol {
    var side: Double { get }
    }

    struct Square : SquareProtocol {
    public let side

    init() {
    self.init(side: 0.0)
    }

    init(side: Double) {
    self.side = side
    }

    func growingSize(by factor: Double) -> Square {
    return Square(side: Rectangle(height: side, width: side).growingSize(by: factor).width)
    }
    }

  • Ou :



    protocol SquareProtocol {
    var side: Double { get }
    }

    struct Square : SquareProtocol {
    public let side

    init() {
    self.init(side: 0.0)
    }

    init(side: Double) {
    self.side = side
    }

    func growingSize(by factor: Double) -> Square {
    return Square(side: side * factor)
    }
    }
  • Joanna CarterJoanna Carter Membre, Modérateur

    Mais, avec ceux, on perde le GenericFormProtocol  ::)


  • JérémyJérémy Membre
    février 2017 modifié #19


    Mais, avec ceux, on perde le GenericFormProtocol  ::)




     


    Bah non, si tu fais ça :



    protocol SquareProtocol: GenericFormProtocol {
    var side: Double { get }
    }

    struct Square : SquareProtocol {
    public let side

    init() {
    self.init(side: 0.0)
    }

    init(side: Double) {
    self.side = side
    }

    func growingSize(by factor: Double) -> Square {
    return Square(side: side * factor)
    }
    }

  • Joanna CarterJoanna Carter Membre, Modérateur

    Bah ouais ! Mais ce n'était ce que tu as écrit  :-*




  • Bah ouais ! Mais ce n'était ce que tu as écrit  :-*




     


    Petite erreur d'adaptation... 8--)

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