30

Moumou :
Ben tu encapsules ton type dans une classe, et c'est gagné, non ?

C'est gagné seulement quand tu as besoin d'un sous-type, pas quand tu as besoin d'un sur-type... (i.e. tu peux pas t'en servir comme raccourci pour les paramètres d'une fonction)


D'ailleurs c'est marrant, le Java n'a pas une inférence de type bottom-up :
public static <K,V> Map<K,V> newHashMap() {
return new HashMap<K,V>();
}

You can use it to safely avoid entering the type parameters twice:
Map<Socket, Future<String>> socketOwner = Util.newHashMap();
i.e. le fait que l'appel soit stocké dans un Map<Socket, Future<String>> change la sémantique de l'appel fou2 (bon en fait heureusement tout se passe bien parce qu'aucun ClasseGénérique<U> ne peut être un sur-type strict de ClasseGénérique<String>, sauf si U est un wildcard smile en revanche ça exclut toute possibilité de conversion automatique entre classes dans une révision future de Java...)

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

31

smeet : oui, mais ça ne résout pas tous les problèmes :

class ClassA<E>
 implements iterable<a<E>>
{
 public ClassA(..)
 {
  ll= new  LinkedList<ClassA<E>>() ;
 }
 public iterator<ClassA<E>> iterator()
 {
  return ll.iterator() ;
 }

 LinkedList<ClassA<E>> ll ;
}



class ClassAInteger
  extends ClassA<Integer>
{
  public ClassAInteger(..) { super() } ;
}





Et maintenant :
ClassAInteger cai= new ClassAInteger() ;
cai.iterator() => renvoit un Iterator<ClassA<Integer>>, pas un Iterator<ClassAInteger>


du coup tu ne peux pas faire un 
for ( ClassAInteger elt : cai )
 System.out.println(elt.toString() )

tu es obligé de faire un
for ( ClassA<Integer> elt : cai )
 System.out.println(elt.toString() )



naturellement j'aimerais me tromper, ça m'arrangerait hehe

32

Oui mais bon je ne comprends pas bien ce que vous voulez. Un typedef ça marche comme ça. Si en caml j'écris typedef myint = int, alors myint n'est ni un surtype, ni un soustype de de int, c'est normal.
avatar
I'm on a boat motherfucker, don't you ever forget

33

./31>
Ben oui, c'est parce que c'est pas un sur-type mais seulement un sous-type : la conversion ne marche que dans un sens (ClassAInteger -> ClassA<Integer>), pas dans l'autre... Le lien d'IBM explique qu'il vaudrait mieux que tu fasses
ClassA<Integer> cai = Util.newClassA();

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

34

Moumou :
Oui mais bon je ne comprends pas bien ce que vous voulez. Un typedef ça marche comme ça. Si en caml j'écris typedef myint = int, alors myint n'est ni un surtype, ni un soustype de de int, c'est normal.

je sais plus comment fait caml, mais en C myint sera exactement le même type que int (donc à la fois un surtype et un sous-type...)

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

35

Pollux :
./31>
Ben oui, c'est parce que c'est pas un sur-type mais seulement un sous-type : la conversion ne marche que dans un sens (ClassAInteger -> ClassA<Integer>), pas dans l'autre...

oui j'ai bien compris, j'ai crossé avec toi (#29) : j'exposais juste un problème de la technique de l'héritage (mal, peut etre grin)


Le lien d'IBM explique qu'il vaudrait mieux que tu fasses
ClassA<Integer> cai = Util.newClassA();

heu, je l'ai lu, mais ça c'est juste pour raccourcir le code à écrire non ?

36

Et sinon on peut toujours écrire

public class MaClasse {
  private ClasseCompliquée datum
  public MaClasse(ClasseCompliquée datum) {
    this.datum = datum;
  }
  <Insérer ici les méthodes que l'on veut, en faisant bien attention aux types de retour>
}


Voilà, et là tu as un alias parfait qui marche où tu veux et quand tu veux. Et si tu veux que MaClasse soit iterable, ben tu lui fais implémenter Iterable, si tu veux que ce soit une List, tu lui fais implémenter List, etc ...

Sinon, on doit aussi pouvoir se démerder avec juste une interface.
avatar
I'm on a boat motherfucker, don't you ever forget

37

Penpen> euh oui, enfin je veux dire que ça peut se généraliser en mettant des variables de type à la place des types que tu veux typedefer... (mais ça vaut vraiment pas un bon typedef, on est bien d'accord)

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

38

Moumou :
Et sinon on peut toujours écrire

public class MaClasse {
  private ClasseCompliquée datum
  public MaClasse(ClasseCompliquée datum) {
    this.datum = datum;
  }
  <Insérer ici les méthodes que l'on veut, en faisant bien attention aux types de retour>
}

Voilà, et là tu as un alias parfait qui marche où tu veux et quand tu veux.

Oui, modulo les conversions explicites entre MaClasse et ClasseCompliquée :/

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

39

Pollux > des variables de type ? (pas compris, dsl)

40

Moumou :
Et si tu veux que MaClasse soit iterable, ben tu lui fais implémenter Iterable, si tu veux que ce soit une List, tu lui fais implémenter List, etc ...

oui menfin l'avantage d'un typedef c'est que ça tient sur une ligne, hein ^^

41

ah oui non mais en fait c'est stupide ce que je dis puisque Java fait ses vérifications de type à la compilation de la classe et pas à l'instanciation... i.e. tu peux évidemment pas faire
<T> T sort(T array) {
   return array.sort();
}
(alors que ça marche en C++)

Enfin tu peux peut-être faire
<T extends MaClasseSuperCompliquée> T sort(T array) {
   return array.sort();
}
?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

42

Ben oui mais justement je croyais que c'était pour ne plus avoir à utiliser ClasseCompliquée ? Si on veut cacher la véritable implémentation, on ne devrait pas pouvoir convertir l'un en l'autre (et d'ailleurs, en fait dans ce cas, une interface est en fait toute designée pour faire ça).
avatar
I'm on a boat motherfucker, don't you ever forget

43

Moumou :
Ben oui mais justement je croyais que c'était pour ne plus avoir à utiliser ClasseCompliquée ? Si on veut cacher la véritable implémentation, on ne devrait pas pouvoir convertir l'un en l'autre (et d'ailleurs, en fait dans ce cas, une interface est en fait toute designée pour faire ça).

Ben non, tu auras certainement besoin d'interfacer avec des packages externes qui se servent, eux, de ClasseCompliquée...
classe MonSocket {
  void ouvrir(String qui,ClasseCompliquée comment) {}
}


Et même si tout le code est de toi, d'ailleurs, ça reste nécessaire si ClasseCompliquée = Array<ClasseSuperLongue> :
<T> T premierélément(Array<T> array) {
  return array[0];
}
On a envie que le typedef puisse être passé à premierélément smile

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

44

Euh d'ailleurs j'ai pas relevé ça, mais j'aurais dû :
Moumou :
Si on veut cacher la véritable implémentation, on ne devrait pas pouvoir convertir l'un en l'autre (et d'ailleurs, en fait dans ce cas, une interface est en fait toute designée pour faire ça).

Ca, c'est pas un typedef (au sens du C en tout cas), c'est une copie (disjointe) d'un type. C'est ce qui se passerait si tu copie-collais ta classe dans un fichier différent avec un nom différent, et je vois pas vraiment de situation où ce genre de chose serait utile confus

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

45

./43 > Ben non, pas si tu entends cacher l'implémentation. C'est de l'abstraction. Si en revanche tu sais que quelle que soit l'implémentation tu devras retourner à un moment un Array<ClasseSuperLongue>, alors ça veut dire que dans ton interface il doit y avoir une méthode getArrayOfClasseSuperLongue. Dans tous les cas, si tu caches l'implémentation, tu ne dois pas attendre quoi que ce soit de la classe finale.

Sauf si l'interface hérite d'autre interfaces. Ainsi si ton interface hérite d'Iterable, alors toute méthode qui attend un Iterable<E> marchera.

Je ne comprends pas ce qui te choque là dedans, c'est la base de l'OOP.
avatar
I'm on a boat motherfucker, don't you ever forget

46

Ce qui me choque, c'est que tu ne veux pas masquer les interfaces de la classe, mais par contre tu veux masquer la classe de départ et ses classes dérivées, pourquoi confus La classe se comporte pareil, a rigoureusement la même sémantique, pourquoi voudrais-tu masquer quoi que ce soit confus Si la classe compliquée est une Socket, pourquoi dire que ton typedef n'en est plus un, alors que c'est "moralement" bien un Socket ? Qu'est-ce que tu veux "abstraire" ? Tu voudrais que chaque classe comme Socket ait sa propre interface ISocket pour dire que oui oui c'est bien un Socket, et qu'il faut que chaque fonction prenant en argument un Socket prenne plutôt un ISocket, pour ne pas dépendre de l'implémentation particulière du Socket ? Quel serait l'intérêt, alors qu'on dispose déjà de membres private/protected pour que justement l'interface d'une classe comme Socket soit "propre" et ne contienne rien en trop, et que donc ce n'est pas nécessaire de faire un ISocket contenant rigoureusement tous les membres publics de Socket et de cacher le Socket derrière un ISocket ?

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

47

Je ne comprends pas l'intérêt d'aliaser une classe si on compte l'utiliser avec la même sémantique. Prenons l'exemple d'une liste de couples. Si tu veux l'utiliser en tant que liste de couples, et uniquement en tant que tel (par exemple pour calculer sa longueur), alors je ne vois pas l'intérêt de l'aliaser. Si tu veux l'utiliser en tant que table d'associations, alors moralement tu ne manipules plus une liste de couples, mais une implémentation particulière de l'interface "table d'associations".

Si tu veux faire un alias juste pour réduire la quantité de code à écrire, alors d'une part ton IDE est naze, et d'autre part un simple préprocesseur peut s'en occuper avant la compilation.
avatar
I'm on a boat motherfucker, don't you ever forget

48

Moumou
:Je ne comprends pas l'intérêt d'aliaser une classe si on compte l'utiliser avec la même sémantique. Prenons l'exemple d'une liste de couples. Si tu veux l'utiliser en tant que liste de couples, et uniquement en tant que tel (par exemple pour calculer sa longueur), alors je ne vois pas l'intérêt de l'aliaser.

Ne pas dupliquer l'information : de même que, je sais pas, en C tu évites de dupliquer l'information qu'est la longueur d'un buffer de taille fixe et tu utilises un #define plutôt qu'une constante hardcodée...
Si tu veux l'utiliser en tant que table d'associations, alors moralement tu ne manipules plus une liste de couples, mais une implémentation particulière de l'interface "table d'associations".

Ah oui, on est bien d'accord, mais ce n'est pas de ça qu'on parle smile Et si tu voulais en faire une table d'associations, tu aurais sans doute qqs méthodes à rajouter et tout ça, et d'autres à supprimer pour maintenir les invariants qui t'intéressent...
[EDIT : ah, je viens de relire ./24, et effectivement j'avais pas fait gaffe à la dernière phrase de Pen^2 : en effet soit on ne veut pas cacher le type et c'est juste une abréviation [publique et immuable] du vrai type, qui reste cependant connu de l'utilisateur de la classe, soit on veut cacher le type et alors il faut adopter ta solution -- enfin bref on parlait pas de la même chose triso]
Si tu veux faire un alias juste pour réduire la quantité de code à écrire, alors d'une part ton IDE est naze, et d'autre part un simple préprocesseur peut s'en occuper avant la compilation.

- qu'est-ce que l'IDE apporterait ? 1] si je veux changer le type d'une variable qui est recopié un peu partout, comment l'IDE peut savoir quels endroits devraient effectivement être changés ? et 2] ça n'améliorerait pas la lisibilité
- oui, exactement (modulo le fait que les typedefs en question devraient avoir une portée analogue à celle des variables), c'est juste que c'est dommage que ça n'existe pas en standard...

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

49

Pollux :
Ne pas dupliquer l'information : de même que, je sais pas, en C tu évites de dupliquer l'information qu'est la longueur d'un buffer de taille fixe et tu utilises un #define plutôt qu'une constante hardcodée...

Oui, parce que la longueur du buffer peut changer. Tandis que le dit type, lui, ne changera pas, sauf si tu ne te sers pas de ce type pour sa sémantique à lui mais parce qu'il remplit le rôle d'une interface quelconque.
Pollux :
1] si je veux changer le type d'une variable qui est recopié un peu partout, comment l'IDE peut savoir quels endroits devraient effectivement être changés ?

Je ne vois pas bien ce que tu veux dire, en quoi aliaser un type t'aiderait à changer le type de la dite variable ?
Pollux :
2] ça n'améliorerait pas la lisibilité

Ben je vois pas en quoi ça améliorerait la lisibilité de renommer List<Couple<String,Int>> en PlopProut si effectivement tu t'en sers uniquement comme d'un List<Couple<String,Int>>. Justement, ça demanderait de revenir chercher les informations à l'endroit de la définition du type.
avatar
I'm on a boat motherfucker, don't you ever forget

50

Moumou
:
Pollux :
Ne pas dupliquer l'information : de même que, je sais pas, en C tu évites de dupliquer l'information qu'est la longueur d'un buffer de taille fixe et tu utilises un #define plutôt qu'une constante hardcodée...
Oui, parce que la longueur du buffer peut changer. Tandis que le dit type, lui, ne changera pas, sauf si tu ne te sers pas de ce type pour sa sémantique à lui mais parce qu'il remplit le rôle d'une interface quelconque.

Ah bon, c'est rare de devoir changer le type d'une variable confus
Je sais pas moi, c'est courant de devoir changer un std::vector (tableau dont on peut efficacement insérer/supprimer des éléments seulement à la fin) en std::deque (tableau dont on peut efficacement insérer/supprimer des éléments à chacun des deux bouts), puis ensuite peut-être qu'on peut avoir besoin d'évoluer vers un tableau "sparse" parce qu'il y aura en fait des trous avec pleins de zéros consécutifs...

Pollux :
1] si je veux changer le type d'une variable qui est recopié un peu partout, comment l'IDE peut savoir quels endroits devraient effectivement être changés ?
Je ne vois pas bien ce que tu veux dire, en quoi aliaser un type t'aiderait à changer le type de la dite variable ?

De la même façon qu'aliaser "42" en "buffer_size" te permet de changer plus facilement la taille de ton buffer si elle est utilisée à plusieurs endroits...

Pollux :
2] ça n'améliorerait pas la lisibilité

Ben je vois pas en quoi ça améliorerait la lisibilité de renommer List<Couple<String,Int>> en PlopProut si effectivement tu t'en sers uniquement comme d'un List<Couple<String,Int>>. Justement, ça demanderait de revenir chercher les informations à l'endroit de la définition du type.

De même qu'écrire
const int buffer_size = 42;
n'a évidemment aucun intérêt puisque je ne me sers de buffer_size que comme de 42. En plus, c'est encore pire, parce que 42, on voit du premier coup d'oeil que c'est un entier, alors que "buffer_size", on est obligé de revenir chercher des informations à l'endroit de la définition de la variable cheeky Les constantes, çay mal, hardcodez tout oui


Malheureusement si on veut éviter la duplication tout en faisant en sorte qu'on puisse voir du premier coup d'oeil la définition, il faut que le code source soit un DAG et pas un arbre tsss

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

51

Pollux :
Je sais pas moi, c'est courant de devoir changer un std::vector (tableau dont on peut efficacement insérer/supprimer des éléments seulement à la fin) en std::deque (tableau dont on peut efficacement insérer/supprimer des éléments à chacun des deux bouts), puis ensuite peut-être qu'on peut avoir besoin d'évoluer vers un tableau "sparse" parce qu'il y aura en fait des trous avec pleins de zéros consécutifs...

Ben c'est parce que tu n'as pas abstrait suffisamment. Tu aurais du utiliser l'interface Container dès le départ, parce que c'est la sémantique que tu veux (ie un truc qui contient des choses, et l'implémentation peut changer à tout moment).

(le reste de ton post ne sert à rien tant que tu ne m'expliques pas pourquoi je devrais changer le type d'une variable (en imaginant que j'ai bien fait mon design))
avatar
I'm on a boat motherfucker, don't you ever forget

52

Moumou
:
Pollux :
Je sais pas moi, c'est courant de devoir changer un std::vector (tableau dont on peut efficacement insérer/supprimer des éléments seulement à la fin) en std::deque (tableau dont on peut efficacement insérer/supprimer des éléments à chacun des deux bouts), puis ensuite peut-être qu'on peut avoir besoin d'évoluer vers un tableau "sparse" parce qu'il y aura en fait des trous avec pleins de zéros consécutifs...

Ben c'est parce que tu n'as pas abstrait suffisamment. Tu aurais du utiliser l'interface Container dès le départ, parce que c'est la sémantique que tu veux (ie un truc qui contient des choses, et l'implémentation peut changer à tout moment).

(le reste de ton post ne sert à rien tant que tu ne m'expliques pas pourquoi je devrais changer le type d'une variable (en imaginant que j'ai bien fait mon design))

T'es pas d'accord ?
avatar
I'm on a boat motherfucker, don't you ever forget

53

Ben nan, là c'est vrai que mon exemple était mal choisi dans la mesure où un std::vector et un std::deque sont complètement interchangeables modulo leur temps d'exécution (encore que, c'est faux par exemple en C++ puisque push_front/pop_front n'existent pas pour std::vector), mais je sais pas, par exemple passer d'un ensemble à un multiset, d'un entier sur 32 bits à un entier multiprécision, d'un entier multiprécision à un rationnel, etc...

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

54

Un ensemble et un multiset sont, en java, deux collections.

Et si tu veux faire des calculs, en vrai, tu n'as pas besoin de savoir à l'avance si ce sera des entiers sur 32 bits, multiprécision, ou je ne sais quoi : tu as juste besoin de savoir que tu manipules des objets addables, subtractables, etc ...
avatar
I'm on a boat motherfucker, don't you ever forget

55

Moumou
: Un ensemble et un multiset sont, en java, deux collections.

Ce n'est pas pour autant que ça résume les méthodes offertes par un Set. Une collection, c'est quelque chose de très, très général, et du coup ça n'exploite pas les spécificités de toutes les collections. Par exemple dans le cas du multiset tu ne peux pas connaître l'ordre de multiplicité d'un élément qui t'intéresse... (sans même parler de méthodes comme headSet() et subSet(), qui ne sont pas présentes dans Collection)
Et si tu veux faire des calculs, en vrai, tu n'as pas besoin de savoir à l'avance si ce sera des entiers sur 32 bits, multiprécision, ou je ne sais quoi : tu as juste besoin de savoir que tu manipules des objets addables, subtractables, etc ...

Ah bon ? C'est interdit de vouloir connaître le dénominateur d'une fraction ? (enfin tu me diras qu'on peut toujours plonger l'un dans l'autre, mais malheureusement en Java on ne peut pas prendre une classe d'un autre package et lui rajouter des interfaces avec des méthodes bidon en plus style "denominateur(){return 1;}")

Par contre je suis d'accord avec toi pour la distinction entier 32 bit / entier multiprécision, mais jusqu'à preuve du contraire pour des raisons de performance c'est toujours Integer et pas Addable ou je ne sais quoi qui est utilisé (d'ailleurs si tu veux passer à Addable tu auras plein de problèmes pour interfacer avec la bibliothèque std Java), donc quand tu veux passer à du multiprécision, tu es bien obligé de changer le type des données :/

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

56

Je maintiens qu'idéalement un programme OO ça se pense uniquement en terme d'interfaces. Idéalement toute fonction devrait prendre un argument dont le type est une interface (c'est la définition d'interface après tout, c'est ce qui définit à quelle condition une classe peut envoyer tel message (ie appeler telle méthode) à une autre). Et ensuite vient la phase d'implémentation. C'est la meilleure façon de programmer en OO.
avatar
I'm on a boat motherfucker, don't you ever forget

57

Mais Java n'est pas un langage idéal, Moumou triso Donc je maintiens ce que je dis, comme on ne peut pas définir de nouvelles interfaces pour une classe 3rd-party, les typedefs seraient bel et bien utiles en Java tongue


Cela dit, dans le fond je suis d'accord avec toi : la plupart des langages OO compilés verrouillent complètement toutes les classes déjà écrites, et du coup on ne peut rien y ajouter (pas d'interfaces supplémentaires, pas de "dénominateur = 1"). Ruby est rigolo parce qu'il permet de rajouter n'importe quoi à une classe existante, c'est souvent utile (par contre il n'y a pas de notion d'interface à proprement parler, tout est typé dynamiquement de façon un peu bordélique ["duck typing"])

En revanche, je pense que même dans ton langage idéal qui permettrait de plonger les entiers dans les rationnels, il faut bien trouver une limite à ton plongement, par exemple tu vas peut-être pas plonger dans les quaternions sur Q[sqrt(a1),...sqrt(an)] (on sait jamais ce dont on peut avoir besoin plus tard triso). Et cette limite, elle va dépendre des évolutions de ton programme (est-ce que ton algorithme d'Euclide doit juste marcher sur des entiers ? ou est-ce qu'il faut prévoir de le généraliser à des rationnels ? on peut d'ailleurs se dire que si ton programme ne marche qu'avec des entiers, ce serait plus logique d'exiger que l'interface soit celle des entiers et pas une autre... et donc le type de tes données serait bien amené à évoluer ^^)

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

58

Bon, tu peux créer une classe qui extends ta classe 3rd party et qui implements ton interface. Donc tout va bien happy

Et sinon dans le langage idéal bah la question à se poser c'est pas "sur quoi vais-je utiliser mon algorithme ?" mais "de quelle propriété ai-je besoin sur mes arguments pour que mon algorithme marche ?".
avatar
I'm on a boat motherfucker, don't you ever forget

59

Moumou :
Bon, tu peux créer une classe qui extends ta classe 3rd party et qui implements ton interface. Donc tout va bien happy

Non, ça ne va pas bien du tout, tu ne peux pas importer un objet 3rd party déjà existant vers ce modèle (et pour cause, si l'objet est d'une classe dérivée de la classe 3rd party, il aura du mal à dériver aussi de ta classe extendée ^^)

Et sinon dans le langage idéal bah la question à se poser c'est pas "sur quoi vais-je utiliser mon algorithme ?" mais "de quelle propriété ai-je besoin sur mes arguments pour que mon algorithme marche ?".

Ben oui, c'est bien ce que je dis, mais justement, cette propriété évoluera *forcément* avec le temps, donc dire que "les types sont fixes si tu conçois bien ton programme" est complètement faux...

« The biggest civil liberty of all is not to be killed by a terrorist. » (Geoff Hoon, ministre des transports anglais)

60

Pollux :
Non, ça ne va pas bien du tout, tu ne peux pas importer un objet 3rd party déjà existant vers ce modèle (et pour cause, si l'objet est d'une classe dérivée de la classe 3rd party, il aura du mal à dériver aussi de ta classe extendée ^^)

Boah, il suffit de faire un constructeur par défaut pour ta classe dérivée, qui prend en entrée un argument de la classe originale happy
Pollux :
Ben oui, c'est bien ce que je dis, mais justement, cette propriété évoluera *forcément* avec le temps, donc dire que "les types sont fixes si tu conçois bien ton programme" est complètement faux...

Pas si tu trouves les conditions non pas suffisantes, mais nécessaires happy
avatar
I'm on a boat motherfucker, don't you ever forget