Laboratoire 2 - Recettes de brochettes de fruits variés.

Exercices avancés sur la généricité.

Ingrédients

  • Des classes de fruits: Fraise, Banane et Orange et leur super-classe Fruit. Comme on peut peler les bananes et les oranges (mais pas les fraises), on ajoute une super-classe commune FruitAPeler.
  • Une classe Brochette. On peut embrocher et débrocher des fruits.

Recette 1 - brochette fade.

  • Dans le langage Java, développez les classes de fruits.
  • Développez un classe brochette non-générique (les paramètres et retours des opérations sont typés par Fruit).
  • Développez le code d’exemple suivant exemple_banane (il devrait fonctionner).
// Pseudocode. Ajoutez les casts qui vont bien si nécessaires.
void exemple_banane() {
    // Créer une brochette de fruits.
    Brochette brochette = new Brochette();
    // Créer une banane.
    Banane banane = new Banane();
    // Embrocher la banane.
    brochette.embrocher(banane);
    // Débrocher un fruit.
    FruitAPeler f = brochette.debrocher();
    // Peler le fruit débroché.
    f.peler()
}
  • Développez un exemple similaire exemple_fruit mais qui prend un fruit en paramètre:
// Pseudocode. Ajoutez les casts qui vont bien si nécessaires.
void exemple_fruit(Fruit fruit) {
    // Créer une brochette de fruits.
    Brochette brochette = new Brochette();
    // Embrocher le fruit
    brochette.embrocher(fruit);
    // Débrocher un fruit.
    FruitAPeler f = brochette.debrocher();
    // Peler le fruit débroché.
    f.peler()
}
  • Développez le programme principal qui invoque exemple_banane, invoque exemple_fruit avec une instance de banane, puis invoque exemple_fruit avec une instance de fraise.
  • Répétez ces opérations (sans utiliser de généricité) pour les langage C++, C#, Python (ou Ruby) et Nit (ou Eiffel).

Discussion

Ici on a deux morceaux de code très similaires exemple_banane et exemple_fruit. Le premier est systématiquement fonctionnel: il ne peut pas échouer lors de l'exécution. Le second est partiellement fonctionnel: il y a un risque d'échec fondamental car dans notre modèle (plus ou moins réaliste), il est impossible de peler une fraise, quelque soit la façon dont on s'y prend.

Questions

Pour chacun des langages:

  • Quel est le problème du premier exemple exemple_banane d'un point de vu de la verbosité et de la sûreté des types ?
  • Dans chacun des langages, où (quelle instruction) ont lieu les erreurs dans le second exemple (exemple_fruit) ?
  • Sont-elles statiques ou dynamiques ?

Recette 2 - brochette typique.

  • Dans le langage Java, développez un classe brochette générique BrochetteGen bornée par Fruit.
  • Développez les deux codes d'exemples exemple_banane_p et exemple_fruit_p en utilisant BrochetteGen<FruitAPeler> au lieu de Brochette.
  • Répétez ces opérations pour les langages C++, C# et Nit.

Questions

  • Quels sont les problèmes résolus par la généricité (par rapport à la version non-générique) ?
  • Où ont lieu les erreurs dans le second exemple (exemple_fruit_p) ?
  • Ces erreurs sont-elles statiques ou dynamiques ?
  • Si on avait utilisé BrochetteGen<Fruit> au lieu de BrochetteGen<FruitAPeler> quels problèmes aurait été résolus/non-résolus (par rapport à la version non-générique) ?

Recette 3 - brochette spécialisée

Cette fois-ci on fait varier le type générique. On utilise statiquement une BrochetteGen<Fruit> et dynamiquement une BrochetteGen<FruitAPeler>

Ce qui donne le pseudo-code suivant

void brochette_fp_fruit(Fruit fruit) {
// Pseudocode. Ajoutez les casts qui vont bien.
    BrochetteGen<FruitAPeler> brochette_p = new BrochetteGen<FruitAPeler>();
    BrochetteGen<Fruit> brochette_f = brochette_p;
    brochette_f.embrocher(fruit);
    brochette_p.debrocher().peler();
}
  • Invoquez brochette_fp_fruit avec une banane puis une fraise.
  • Implémentez brochette_fp_fruit dans les 4 langages Java, C++, C# et Nit

Questions

Chacun de ces 4 langages a une sémantique différente par rapport au sous-typage et à la généricité.

  • Quels langages nécessitent de faire un cast pour pouvoir compiler statiquement ?
  • Quels langages génèrent une erreur à l'exécution même quand on exécute avec une banane ?
  • Où sont levés les erreurs dans le cas des fraises ? Chaque erreur ressemble-t-elle au cas de la fraise dans la recette 1 ou au cas de la fraise dans la recette 2 (ou à aucun de ces deux cas) ?

Recette 4 - brochette variée

On continue maintenant de regarder les problèmes de sous-typage des brochettes par rapport aux variations génériques. Pour ce faire, développe des fonction (méthodes/sous-programmes) utilitaires qui acceptent des brochettes passées en paramètre.

Réalisez ces fonctions dans les langages Java, C++, C# et Nit en utilisant en paramètre des BrochetteGen (des types génériques). Réalisez l'équivalent en Python (ou Ruby) en utilisant Brochette la version non générique de la classe.

  • Si besoin, en Java, utilisez les wildcards pour faire de la covariance et contravariance: voir ?, extends et super.
  • Si besoin, en C#, créez des super interfaces covariante et contravariante à BrochetteGen: voir in et out.
  • En Nit, C++ et Python: débrouillez-vous.

Compte Sloubi

Créez une fonction (méthode statique ou fonction hors classe) compte_sloubifuit:

  • qui prend en paramètre une brochette (à vous de déterminer le type statique) ;
  • débroche chacun des fruits ;
  • compte combien il y avait de fruits et affiche la valeur à l'écran.
  • On veut pouvoir appeler cette fonction autant avec une BrochetteGen<Fruit> qu'avec une BrochetteGen<Banane>.

Quel est le "meilleur" type statique du paramètre de la fonction compte_sloubifuit pour le langage considéré ?

// idéalement on veut pouvoir écrire un truc du genre:
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
// embrochage de fruits variés dans bf.
compte_sloubifuit(bf);

// mais aussi quelque chose du genre
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
// embrochage de fruits variés dans bp.
compte_sloubifuit(bp);

Pêle-Mêle

Créez une fonction (méthode statique ou fonction hors classe) pele_mele:

  • qui prend en paramètre une brochette (à vous de déterminer le type statique)
  • pèle tous les fruits de la brochette
  • contrainte: implémentez cette fonction sans utiliser de casts
  • vous ne pouvez pas changer les classes de fruits existantes
  • on veut pouvoir appeler cette fonction autant avec une BrochetteGen<FruitAPeler> qu'avec une BrochetteGen<Banane>.

Quel est le "meilleur" type statique du paramètre de la fonction ? Peut-on vraiment l'invoquer avec une BrochetteGen<Fruit> ? Si non, pourquoi ?

// On veut pouvoir écrire un truc du genre:
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
// embrochage de fruits variés dans bp.
pele_mele(bp);

// On aimerai aussi pouvoir écrire un truc du genre: 
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
// embrochage de fruits variés dans bf.
pele_mele(bf);

Appel de fruits sauvages

Créez une fonction (méthode statique ou fonction hors classe) appel_a_peau:

  • qui prend en paramètre une brochette (à vous de déterminer le type statique)
  • qui embroche une orange puis une banane
  • contrainte: implémentez cette fonction sans utiliser de casts
  • vous ne pouvez pas changer les classes de fruits existantes
  • on veut pouvoir appeler cette fonction autant avec une BrochetteGen<FruitAPeler> qu'avec une BrochetteGen<Fruit>.

Quel est le type meilleur statique du paramètre de la fonction? Peut-on l'invoquer avec une BrochetteGen<Banane>? Si non pourquoi?

// idéalement on veut pouvoir écrire un truc du genre:
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
appel_a_peau(bp); // des fruits variés ont été embrochés.

// et quelque chose du genre
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
appel_a_peau(bf); // des fruits variés ont été embrochés.

Questions

Comparez les solutions de ces langages par rapport à

  • La sûreté statique
  • La verbosité
  • La lourdeur en nombre d'entité du modèle
  • La complexité du système de type sous-jacent

Travail à rendre

Une archive .zip ou .tar.gz contenant:

  • Un répertoire par langage. Exemple nit/
  • Le code source dans le répertoire. Éventuellement dans un seul fichier si le langage le permet. Exemple nit/brochette.nit
  • Il n'y a pas de version différentes à faire à chaque étape : tout peut être mit dans le même projet.
  • Les réponses aux questions doivent être indiquées dans le code source sous forme de commentaires longs.

Critères de qualité:

  • Assurez-vous que le code soit petit, autonome et élégant (lisible et sans superflu)
  • Autant que possible, minimisez les différences entre les langages et les variations (man diff)
  • Identifiez et justifiez et clairement les réponses aux questions