Aller au contenu


Photo

From generic protocol to type !

swift protocol generic inheritance

  • Please log in to reply
20 réponses à ce sujet

#1 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 02 février 2017 - 01:43

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 ?  ::)


Twitter : @jrmguimberteau


#2 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 02 février 2017 - 15:39

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 ?



#3 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 02 février 2017 - 16:18

À 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 ?


Twitter : @jrmguimberteau


#4 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 02 février 2017 - 17:02

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



#5 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 02 février 2017 - 17:30

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). :)


Twitter : @jrmguimberteau


#6 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 344 messages
  • LocationParis

Posté 02 février 2017 - 20:45

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)


#7 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 02 février 2017 - 23:09

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.  :mellow:

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


Twitter : @jrmguimberteau


#8 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 03 février 2017 - 20:32

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


#9 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 février 2017 - 15:40

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


#10 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 06 février 2017 - 13:23

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. :prie!:

 

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)

Twitter : @jrmguimberteau


#11 FKDEV

FKDEV

    Broyeur de fèves

  • Artisan chocolatier
  • PipPipPipPipPipPip
  • 1 660 messages

Posté 06 février 2017 - 16:09

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...ons-and-methods

Cela pourrait surement fonctionner avec un langage vraiment dynamique pour qui Generic resterait un seul protocol paramétrable au moment du runtime.

#12 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 06 février 2017 - 16:39

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... :(


Twitter : @jrmguimberteau


#13 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 06 février 2017 - 18:34

 

Le melon qu'elle a celle la ! xd

 

:clap:  :clap:  :clap:

 

 

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



#14 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 07 février 2017 - 10:11

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

Twitter : @jrmguimberteau


#15 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 07 février 2017 - 10:32

 

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



#16 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 07 février 2017 - 10:52

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 ! :snif:

 

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

Twitter : @jrmguimberteau


#17 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 07 février 2017 - 10:56

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

Twitter : @jrmguimberteau


#18 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 07 février 2017 - 11:17

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



#19 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 342 messages
  • LocationBordeaux

Posté 07 février 2017 - 11:21

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

Twitter : @jrmguimberteau


#20 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 07 février 2017 - 11:33

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







Also tagged with one or more of these keywords: swift, protocol, generic, inheritance

0 utilisateur(s) li(sen)t ce sujet

0 membre(s), 0 invité(s), 0 utilisateur(s) anonyme(s)