Outils pour utilisateurs

Outils du site


cours:informatique:dev:programmation_objet_pharo:210_messages_composition_et_precedence

Messages: composition et précédence

Dans ce cours, nous allons aborder la manière de composer des messages et de voir comment marche la précédence, c'est-à-dire qui prend le pas par rapport à un autre. Si on regarde, la question qui se pose c'est “Qu'est-ce qui se passe quand j'ai une séquence de messages unaires?”

1000 factorial class name.

"Résultat: LargePositiveInteger"

Là, j'ai écrit l'expression suivante 1000 factorial class name. Il se trouve que ça s'exécute exactement de la même manière que si j'avais mis toutes ces parenthèses:

(((1000 factorial) class) name)

Mais on voit que c'est embêtant d'avoir toutes ces parenthèses. Ce que le système va faire, c'est que quand vous avez des messages qui sont d'un même niveau, ça veut dire binaires, unaires ou à mots-clefs, ça marche dans les trois cas, il va toujours les évaluer de gauche à droite.

  • Là, je vais envoyer le message factorial à 1000;
  • Après, je vais envoyer le message class aux résultats de factorial.
  • Et après, je vais envoyer de message name à ce nouvel objet et je vais obtenir le résultat LargePositiveInteger.

Pour info, factoriel 1000, c'est quand même un gros nombre. Vous pouvez aussi essayer avec factoriel 10000 si vous en avez envie, c'est un tout petit peu plus long.

Règle complète de précédence

  • ( ) > Unaire > Binaire > Mots-Clés
  • En cas d'égalité: de gauche à droite

Là on voit qu'on a des parenthèses pour exécuter des expressions en premier lieu, ensuite les messages unaires, ensuite les messages binaires, les messages à mots-clefs et après quand il y a égalité, on fait de gauche à droite.

Regardons un exemple.

2 + 3 squared
" 11"

Là, on a un message unaire: squared, et un message binaire, +. C'est le message unaire qui gagne: d'abord squared et, en second lieu on envoie le message +. On obtient alors 11.

Maintenant, là j'ai un autre cas de figure:

2 raisedTo: 3 + 2
"32"

On va élever à la puissance avec raisedTo:. On a un message binaire et un message à mot-clef, donc c'est le message binaire qui gagne. Premièrement, ça va me faire 5, ensuite, on transmet le message raisedTo:, ça nous retournera 32.

Là, c'est un exemple un peu plus funky:

Color gray - Color white = Color black

"true"

Les couleurs en Pharo sont des objets, donc j'ai la classe couleur. Là, j'ai trois messages unaires, j'ai gray, white et black. Et j'ai deux messages binaires, j'ai = et -. Qu'est-ce qui va se passer ? Le système va d'abord exécuter tous les messages unaires. Je vais obtenir la couleur gray, la couleur white et la couleur black. Là donc, il faut que, maintenant, j'envoie des messages, il me reste à choisir entre le message - et le message =. Ce sont des messages de même précédence donc exécution de gauche à droite). Là, je vais envoyer message moins à l'objet gray avec comme argument white, ça va me donner la couleur black. Après, je vais comparer mes deux couleurs et ça me donne que black = black, donc c'est vrai.

Ici, c'est plus un exemple pour vous montrer qu'on peut aussi faire de la conversion automatique avec Pharo, c'est une sorte de clin d'œil un petit peu.

1 class.
"SmallInteger"

1 class maxVal.
"1073741823"

(1 class maxVal + 1) class.
"LargePositiveInteger"

Entre parenthèses on a l' expression 1 class maxVal + 1, comprenant deux messages unaires, class et maxVal, et puis un message binaire +. Le message class est envoyé à l'entier 1, il retourne sa classe SmallInteger. Le message maxVal est ensuite envoyé à la classe. La valeur retournée est le plus grand nombre que peut encoder la classe . On envoit ensuite le message + avec l'argument 1 au résultat précédent. On obtient un résultat ne pouvant plus être contenu dans un petit entier. Finalement, on demande la classe de cet objet via le message class. On obtient LargePositiveInteger.

Autre exemple:

" L'expression suivante retourne une erreur"
0@0 extent: 100@100 bottomRight

"Message not understood 100 does not understand bottomRight"

Oncréé un rectangle et on veut obtenir le point en bas à droite du rectangle. Lorsqu'on éxécute le code ci-dessus, une exception est levée. Le système me dit “Je ne comprends pas: 100 ne comprend pas le message “bottomRight” Pourquoi? Parce que bottomRight, c'est un message unaire et il est exécuté avant tous les autres, donc il est envoyé à 100 qui est son receveur. Mais 100 ne comprend pas cette API puisque c'est une API de la classe rectangle. Là, on est obligé de mettre des parenthèses:

" L'expression suivante retourne une erreur"
(0@0 extent: 100@100) bottomRight

ce que j'ai fait ici dans cette expression. Comment ça se passe ? L'expression parenthésée est exécutée en premier, dans l'expression parenthésée, j'ai deux messages binaires qui sont exécutés qui me créent des points. Donc, j'ai mes deux points qui sont créés. J'envoie le message extent à un point qui me crée un rectangle. L'idée, c'est que j'ai un point 0,0 et puis après je lui passe extent: avec le paramètre 100,100, donc ça va me donner ce rectangle-là. Et maintenant, je lui demande quelle est la valeur. BottomRight à la fin vient d'envoyer un rectangle et il me donne la valeur du point qui est ici et qui est bien le point 100,100.

Le prix de la simplicité

Ce que je vous ai dit dans Pharo, c'est que c'était très simple: en fait, il n'y a que des messages. Donc, + est un message comme les autres, il n'y a pas de précédence mathématique. L'avantage, c'est qu'on peut utiliser + pour faire du domain specific langages, on peut utiliser + entre des objets qui n'ont rien à voir avec des objets mathématiques. Typiquement en Java, vous ne pouvez pas le faire. En C++, vous c'est possible en redéfinissant les opérateurs. En Pharo, la solution, c'était + est un message comme les autres. C'est un choix de simplicité. Par contre, il y a un prix, c'est qu'il n'y a pas de précédence mathématique: autrement dit Pharo n'applique pas la priorité naturelle des opérateurs mathématique.

 3 + 2 * 10
"50"

3 + (2 * 10)
"23"

Dans cette expression, j'ai deux opérateurs ou deux messages binaires. J'exécute de gauche à droite. Je fais 5 et puis ça me rend 50 et ce n'est pas ce qu'on m'a appris à l'école. Pour obtenir ça, il va falloir forcer la priorité en introduisant des parenthèses, donc je vais utiliser des parenthèses autour de multiplié.

Donc effectivement, il faut faire attention lorsqu' on manipule des opérations arithmétiques dans Pharo parce que les opérateurs mathématiques sont juste des messages.

Un autre exemple:

1/3 + 2/3
"7/9"

(1/3) + (2/3)
"1"

si on saisit directement l'expression 1/3 + 2/3., on ne va pas obtenir ce qu'il faut parce que le système va exécuter les messages binaires de même priorité de gauche à droite. Ce sont les parenthèses qui permettront d'obtenir le résultat mathématiquement correct.

Là, on a vraiment un point intéressant à soulever, c'est qu'on j'obtiens l'entier 1 quand je fais la somme de fractions 1/3 + 2/3. On n'obtiens pas une valeur flotante 1,0 quelque chose ou d'approchant tel que 0,999 etc. Ce sont des fractions, elles sont exactes et on obtient des calculs exacts.

En résumé, on aura vu qu'il y a trois sortes de messages, maintenant vous devez le savoir:

  • unaires,
  • binaires
  • mots-clefs.
  • On exécute de manière prioritaire les expressions entre parenthèses, les messages unaires, binaires et à mots-clefs.
  • Quand on est à égalité, si j'ai deux messages unaires, je vais le faire de gauche à droite.
  • Il n'y a pas de précédence mathématique parce que les messages mathématiques sont juste des messages comme les autres.
  • Et ce qui est différent d'avec la plupart des langages, c'est que les arguments sont placés à l'intérieur de la structure du message comme dans beetween: and:
cours/informatique/dev/programmation_objet_pharo/210_messages_composition_et_precedence.txt · Dernière modification : 2022/07/17 18:32 de yoann