Tuteur pour le langage SMALLTALK sml

novalid      

Texte du Tuteur écrit par Gilles HUNAULT

Liste des autres tuteurs (langages, logiciels, systèmes d'exploitations...)


Autres références sur le Web pour SmallTalk

Table des Matières

SmallTalk et la programmation objets "pure"

L'environnement SmallTalk

Le langage de SmallTalk

Quelques exemples en SmallTalk

1. exemples élémentaires

2. un bonjour sophistiqué

3. la table de multiplication dans une fenetre

SMALLTALK ET LA PROGRAMMATION OBJETS

Dans les années 90, le développement de programmes informatiques se faisait avec d'un coté un éditeur de texte, de l'autre un compilateur et un éditeur de liens. Pour des programmes de plusieurs milliers de lignes, la lisibilité, la relecture et la maintenance de code était un problème. Un des objectifs de la programmation objets a donc été de gérer ce problème.

L'analyse des sources des programmes a mis en évidence des "écueils conceptuels" tels que la duplication de code et le saucissonnage de code. La duplication de code survient lorsque pour des actions similaires (comme trier un tableau de fournisseurs et trier un tableau de clients), on écrit à peu près les mêmes instructions, à quelques modifications près. Le développement se fait d'ailleurs de façon asynchrone : on écrit par exemple le sous-programme de tri du tableau des clients, on le teste, on le met au point puis on le recopie en changeant de nom et on effectue les quelques modifications nécessaires pour qu'il trie le tableau des fournisseurs. Le saucissonnage de code intervient au contraire lorsqu'on garde un seul sous-programmme pour tout gérer : on passe alors sans arret d'un paquet d'instructions communes à des structures de cas multiples en pagaille. Le saucissonnage intervient aussi lorsqu'on qu'on doit gérer de nombreux paramètres.

De plus, la programmation classique des interfaces utilisateurs oblige à un code important car les fenêtres d'applications pour les environnements graphiques utilisent de nombreuses sous-fenetres similaires avec des composants standards comme les boutons, les menus. Chaque fenêtre a son jeu de paramètres multiples comme la taille, la largeur, la hauteur de la fenêtre, le nom de la police de caractère, la taille de la police, les attributs logiques de passage en gras, en souligné, etc. ce qui entraine des listes des paramètres monstrueuses.

Face à ces problèmes de taille de code et face donc à l'arrivée des interfaces utilisateurs graphiques, il a fallu repenser le schéma de développement d'une application : au lieu de développer des programmes qui traitent des données, c'est à dire au lieu d'accorder la priorité aux programmes, on a décidé de gérer des objets (données) qui comportent des méthodes (programmes) et donc de se focaliser sur les données. Structurer un programme en sous-programmes était une obligation. Quant aux données, elles pouvaient ne pas être structurées du tout ou être structurées de facon incohérente sans qu'on n'y puisse rien.

La programmation objets a donc imposé de gérer les données, en organisant des modèles de données, nommées classes d'objets qui comportent des variables ou "champs" et des sous-programmes nommés "méthodes" dans la terminologie objets.

Des actions similaires se voient alors donner le même nom, c'est ce qu'on appelle le polymorphisme. Un comportement défini pour un modèle est automatiquement détecté et repris par un sous-modèle : on dit qu'il y a héritage des actions. De plus, les modèles peuvent avoir des valeurs par défaut. Au lieu de donner tous les paramètres par exemple pour une fenêtre utilisateur, on peut se contenter de ne donner que les valeurs différentes des valeurs par défaut d'où un code plus court.

La plupart des langages existant dans les années 90 (comme C et Pascal) ne pouvaient pas implémenter ces concepts directement et on a donc "bricolé" ces langages pour qu'ils puissent gérer les objets. Par exemple, le langage pascal est devenu le langage pascal orienté objets (comme dans Turbo Pascal pour Windows) : un objet est simplement défini comme un type particulier qui permet de coiffer les autres déclarations... Signalons qu'avec l'arrivée des développements visuels (on compose à l'écran les fenêtres d'application et la machine écrit le code correspondant) les objets fondamentaux comme les listes déroulantes, les barres d'outils etc. sont passés dans le standard des objets élémentaires et les langages ont encore effectué une évolution. Ainsi Turbo Pascal pour Windows est devenu Delphi qui est à la fois un langage et un environnement de développement basé sur le langage pascal.

SmallTalk lui, a utilisé une approche radicalement différente : conçu dés le départ pour gérer des objets (il s'agissait initialement de gérer de "vrais" objets réels comme des imprimantes, des photocopieuses) avec une interface ergonomique, il a tout de suite mis en place les concepts d'un langage objet "pur" : on commence par les objets, on finit par les programmes. Cela oblige les programmeurs classiques à changer leur mode d'écriture mais cela donne un langage très cohérent doublé d'un environnement adapté à l'écriture de classes et de méthodes...

L'ENVIRONNEMENT SMALLTALK

SmallTalk n'est donc pas uniquement un langage mais aussi un environnement de développement. Nous présentons ici la version "historique" SmallTalk V pour Windows telle qu'elle fonctionnait dès 1991 sur un PC avec un processeur 286 (si, si cela a existé !).

sm1

Un tel environnement n'a rien à envier aux EDI (environnement de développement intégré) des années 2000, sauf pour les interfaces visuelles, ce qu'on peut aisément lui pardonner vu son grand age... De plus elle fonctionne encore très bien après l'an 2000, ce qui la rend sympathique !

Lorsqu'on lance SmallTalk, une fenêtre d'interaction apparait, nommée Transcript. On peut y écrire des commentaires entre des guillemets, écrire des instructions que SmallTalk viendra exécuter.

sm1

Par exemple si on écrit dans la fenêtre de Transcript le texte 'bonjour' size et si on sélectionne ce texte à la souris, un clic sur le bouton droit permet d'éxécuter l'instruction avec "Show it". SmallTalk écrit alors 7 ce qui signifie que la longueur de la chaine 'bonjour' est 7.

sm1

La différence entre le mode "Do it" et le mode "Show it" est importante : "Do it" exécute l'instruction mais ne dit rien par défaut, "Show it" affiche la derniere expression renvoyée par l'instruction. Si par exemple on trie une liste et qu'on met le résultat dans un fichier, "Do it" pourra se contenter de tout effectuer sans affichage intempestif alors que "Show it" viendra éventuellement afficher toute la liste triée, ce qui peut encombrer la fenêtre de Transcript.

On n'écrit pas de "programme" en SmallTalk mais des sous-programmes (on dit en fait une méthode plutot que sous-programme) relatifs à une classe d'objets choisis. Pour cela, après avoir réfléchi à la classe d'objets à utiliser, on utilise le gestionnaire de classe qu'on apppelle par le menu File / Browse Classes.

sm1

Une nouvelle fenêtre apparait, composée de 4 panneaux (pane en anglais). Les 3 panneaux du haut permettent de choisir les classes et les méthodes, celui du bas à écrire le texte de la méthode.

sm1

Soit la classe existe déjà (comme par exemple pour les nombres) soit on l'invente à l'aide de la commande Classes / Add Subclass.

sm1

Toutes les classes (il y en a en gros 170) ne sont pas affichées (on s'en rend compte à cause des symboles ... en fin de nom de classe). Un double-clic sur le nom de la classe affiche les sous-classes. Si on ne sait pas où est une sous-classe (par exemple pour un(e) débutant(e) il n'est pas du tout évident que la classe des nombres "Number" est une sous-classe de la classe "Magnitude"), on peut utiliser la commande Class / Find Class ou consulter une liste papier ou une page web comme celle du professeur Naugler pour avoir toute la hiérarchie des classes.

Pour écrire une méthode, donc, une fois la classe choisie, on rend actif le panneau inférieur du gestionnaire de classe (qui est une fenêtre d'édition) à l'aide de la commande Methods / New Method si la méthode n'existe pas. Si la méthode existe, cliquer sur son nom affiche le texte de la méthode. SmallTalk sait se positionner sur la première méthode dont l'initiale est tapée au clavier (comme pour les Explorateurs Windows des années 2000) : ainsi, pour trouver la première méthode dont le nom commence par m on tape m dans la liste de méthodes.

sm1

La copie écran ci-dessous montre par exemple l'édition de la méthode moyenne dans la classe "Collection".

sm1

Lorsqu'on enregistre la méthode par un clic-droit et la commande Save, le nom de la méthode apparait dans le panneau supérieur gauche du gestionnaire de classes : le sous-programme fait alors partie de SmallTalk au meme titre que les autres méthodes de SmallTalk.

sm1

On notera que le texte des toutes les autres méthodes de SmallTalk (sauf quelques méthode dites "primitives") s'affiche dès qu'on clique sur son nom dans le panneau supérieur droit du gestionnaire de classes : on a donc accès au code-source de tout le langage, ce qui est un avantage énorme pour un développeur professionnel puisqu'on a la possibilité de modifier tout le langage.

Pour quitter SmalTalk, on clique sur l'icone de SmallTalk dans la fenêtre de Transcript au-dessus du menu File (et oui, la CUA avec le menu Fichier / Quitter n'existait pas à l'époque) et on choisit Exit SmallTalk. Il reste alors à valider nos modifications en faisant un "backup" de l'image de l'environnement ou à les ignorer et le tour est joué.

sm1       sm1

Attention : SmallTalk garde une trace de TOUT ce qui a été fait dans le fichier change.log qui ne doit en aucun cas etre modifié (mais c'est un texte lisible qu'on peut recopier et dont on peut modifier la copie). En particulier si on édite en modifiant 5 fois une méthode, on trouvera les 5 versions de la méthode dans change.log ; cela peut-etre pratique pour revenir a une version antérieure, pour archiver les changements (afin de les commenter ultérieurement). Il existe bien sur des instructions pour compresser le log afin qu'il n'ait pas une taille monstrueuse...

LE LANGAGE DE SMALLTALK

A idées nouvelles, langage nouveau est un aphorisme digne de SmallTalk. Dans un langage "classique" on trouve des instructions comme les affectations, les tests, les boucles, puis des appels de sous-programmes. Pas en SmallTalk. En SmallTalk, on écrit des instructions un peu comme dans une langue, avec la règle de grammaire :

une phrase = un sujet, un verbe (et des compléments éventuels)

Dans la terminologie SmallTalk, cela devient

une instruction = un objet, une méthode (et des paramètres éventuels)

Le couple méthode et paramètres éventuels est nommé message transmis à l'objet. Le résultat est appelé réponse de l'objet au message.

Ainsi en programmation traditionnelle, on pouvait écrire

RendVisiteA(Pierre,Paul) ;

avec un sous-programme RendVisiteA et deux paramètres Pierre et Paul. En SmallTalk cela devient

Pierre RendVisiteA: Paul

Pierre est l'objet qui reçoit le message RendVisiteA: Paul. Le calcul 2 + 3 qui n'est pas une instruction dans un langage classique devient une instruction en SmallTalk où le message + 3 est envoyé à l'objet 2, message composé de la méthode + et du paramètre 3.

La lisibilité est accrue par la règle "on met le symbole : si la méthode requiert des paramètres". On est donc censé comprendre la syntaxe à utiliser pour une méthode donnée. L'affectation est la seul exception : les concepteurs de SmallTalk ont laissé le traditionnel := du Pascal au lieu d'un (joli) =:

Ainsi pour affecter à n la longueur (size en anglais) de l'objet x, on écrit n := (x size) qui doit donc s'interpréter comme l'envoi du message := (x size) à l'objet n.

Pour un programmeur classique, il faut donc apprendre à "inverser" les appels classiques de sous programmes. Voici quelques exemples :


     f(x)                     x f

     f(x,y)                   x f: y

     f(x,y,z)                 x f: y with: z

     f(x,g(y))                x f: (y g)

     f(x,g(y,z))              x f: (y g: z)

     f(x,g(y,z,k)             x f: (y g: z and: k)

SmallTalk étant un langage "pur", toute instruction doit passer par le modèle cité. Les test devienent donc des messages passés à la condition, les boucles des messages passés aux bornes de boucle. On doit par exemple traduire le test


     si condition alors

        instructions

     finsi

en l'instruction

     condition ifTrue: [

       instructions

     ].

et la boucle pour traditionnelle

     pour i de1a n

        instructions

     finpour

en l'instruction avec variable locale obligatoire

     (1 to: n) do: [ :i |

        instructions

     ].

On notera que le symbole point (.) sert à indiquer la fin de l'instruction.

Deux difficultés se présentent alors pour apprendre SmallTalk : découvrir les 2000 et quelques méthodes déjà présentes, utiliser au mieux les classes déjà présentes. Par exemple on peut réinventer la fonction puissance dans la classe Float des nombres réels sans savoir qu'elle existe déjà (elle se nomme raisedTo:) dans la classe Number. Ou on peut triturer un tableau dans la classe Array pour avoir des valeurs uniques alors que la classes Set est conçue pour.

Voici à titre d'exemples quelques instructions SmallTalk standards à connaitre


                               Objet        Message

pour afficher l'heure          Date         today

pour extraire la sous-chaine   p            copyFrom: 5 to: 9
des caractères 5 à 9 dans p

pour avoir la valeur numéro i  t            at: i
du tableau t

pour affecter la valeur y      (t at: i)    put: y
en t[i]

pour savoir si x est compris   x            between: a and: b
entre a et b

pour savoir si on est a la     fic          atEnd
fin du fichier nommé fic

pour forcer l'affichage de     x            printString
l'objet x en mode texte

SmallTalk fournit en standard de nombreux objets dont trois objets fondamentaux à savoir Transcript, Prompter et Message. Transcript permet de dialoguer en interactif avec SmallTalk, Prompter gère les fenetres d'interrogation et Message les fenetres d'information. On se rend compte du soin apporté au langage SmallTalk quand on remarque comment on utilise Prompter : pour effectuer une demande dans un langage traditionnel, on doit utiliser une instruction "écrire" pour le texte de la question, une instruction "lire" pour le texte de la réponse avec en principe aucun moyen de fournir une valeur par défaut. Prompter requiert lui, en obligatoire, le texte de la question et une valeur par défaut avec le modèle

Prompter prompt: question default: valeur.

Les exemples "Bonjour" et "Table" montrent comment utiliser ces objets.

QUELQUES EXEMPLES EN SMALLTALK

1. Exemples élémentaires

Un exemple très élémentaire de méthode consiste à calculer le carré d'un nombre. Dans la classe Number, on écrit donc la méthode carré par


 carre
 ^self*self

On notera qu'il n'est pas possible de définir la méthode carre: donc avec le symbole "deux points" car SmallTalk exigerait alors un argument pour la méthode.

Avec un peu plus de lisibilité, on peut détailler en


 carre
   " calcul du carré du nombre récepteur "
   | leNombre sonCarre |
   leNombre := self .
   sonCarre := leNombre * leNombre .
 ^sonCarre

Renvoyons maintenant un nombre cadré sur plusieurs positions avec un caractère variable de remplissage. Toujours dans la classe Number, on écrit donc la méthode format: avec: soit le texte


 format: longueur avec: carRemp
   " cadre sur le nb de caractères demandés : on rajoute "
   " devant le nb autant de fois que nécessaire le caractère"
   " nommé carRemp "
 | s |
 s := self printString.
 [ (s size) > longueur ] whileFalse: [
    s := carRemp , s .
 ]. " fin de tant que "
 ^s

On peut alors demander à SmallTalk d'évaluer

  3.2 format: 10 avec: '*'
pour obtenir
 '********3.2'
Passons maintenant à la conversion en majuscule du premier caractère d'une chaine. Dans la classe String, nous écrivons comme méthode initialeMajuscule soit le texte


 initialeMajuscule
 ^((self at: 1)asUpperCase),(self copyFrom: 2 to: (self size))

Mais la conversion du premier caractère reste alors... un caractère (avec le symbole $ devant). Une vraie conversion peut se faire sur la base de la méthode asUpperCase, soit

 initialeMajuscule
    | answer size index aCharacter |
    size := self size.
    answer := String new: size.
    index := 1.
    [index <= size]
        whileTrue: [
            aCharacter := self at: index.
             (index=1)
               ifTrue: [
                  aCharacter :=  aCharacter asUpperCase]
               ifFalse:[
                  aCharacter :=  aCharacter asLowerCase
               ]. " fin de si "
            answer at: index put: aCharacter.
            index := index + 1].
    ^answer

Enfin, pour terminer nos exemples élémentaires, voici le comptage du nombre de lignes dans un fichier texte : dans la classe Stream, nous écrivons la méthode nombreDeLignes soit le texte


nombredeLignes

| nbl |

nbl := 0.
[self atEnd] whileFalse: [
  nbl := nbl + 1 .
  self nextLine .
]. " fin tant que non fin de fichier "

^nbl

2. Un bonjour sophistiqué

Comme premier exemple un peu soutenu, voici notre bonjour de référence, avec demande du prénom, conversion en majuscule et affichage de la date et de l'heure. La première difficulté vient de l'endroit où écrire la méthode. Le message réduit à la méthode bonjour doit être envoyé à un objet, mais lequel ? A priori, cet objet n'est ni une chaine, ni un nombre, ni ... On invente donc une classe Demo (sous-classe directe de Object). Pour cela dans le gestionnaire de classes, on sélectionne la classe "Objects" et on valide le menu Class / Add Subclass et on tape le mot Demo dans la case prévue à cet effet :

sm1

Une fois le nom validé par "Ok" la classe apparait dans le panneau le plus à gauche qui donne la liste des classes :

Avant la création        
 
Après la création

On peut alors créer une nouvelle méthode pour cette classe avec le menu Methods / New Method et y écrire notre programme, soit le texte


 bonjour " Exemple de commentaire, lire, écrire et affectation "
  | pren |
  Cursor offset: 140 @ 150.
  pren := Prompter  prompt: ' Bonjour. Quel est ton prénom ? '  default: ' euh ?'.
  pren := ' Merci. Au revoir ' , (pren asUpperCase), ' ' , (Date today) printString.
  Cursor offset: 140 @ 300.
  Menu message: pren.

Ce texte peut bien sur etre formaté différement, par exemple comme

 bonjour

 " Exemple de commentaire, lire, écrire et affectation "

  | pren |

  Cursor offset: 140 @ 150.

  pren := Prompter  prompt: ' Bonjour. Quel est ton prénom ? '
                    default: ' euh ?'.

  pren := ' Merci. Au revoir ' ,
          (pren asUpperCase), ' ' ,
          (Date today) printString.

  Cursor offset: 140 @ 300.

  Menu message: pren.

pour mieux faire ressortir les messages dans les instructions.

Pour exécuter notre méthode bonjour, il nous faut un objet de classe Demo. On peut en créer un local et lui envoyer le message bonjour avec les instructions

sm1
qu'il faut exécuter ensemble en sélectionnant tout le bloc d'instructions et en faisant un "Show it" ou un "Do it". A l'exécution, on obtient la fenetre de demande du Prompter

sm1

puis le message de fin envoyé par Menu

sm1

Une meilleure solution pour exécuter notre méthode bonjour est de créer une variable globale. Pour cela, il faut que son nom commence par une majuscule. De plus, il faut valider la création de la variable lorsque SmallTalk pose la question de la création. Par exemple, nous créons ici la variable globale Mademo :

sm1             sm1

et pour utiliser bonjour, il suffit d'évaleur avec "Do it" l'expression Mademo bonjour.

3. La table de multiplication et sa fenetre

Un deuxième exemple non trivial est celui de la table de multiplication avec affichage dans une fenêtre autre que le Transcript. La table de multiplication s'adresse à un entier. Nous commençons par écrire la méthode table dans la classe Integer soit le texte

table
 | fenetre |
 " table de multiplication "
 fenetre := TextWindow  windowLabeled: 'Table de Multiplication '
     frame: ( 210 @ 30 extent: 400 @(Display extent //2)y ).
 fenetre  cr ;
     nextPutAll: ' Table de ' ;
     nextPutAll: (self printString) ;
     cr ;
     cr .
 1 to: 10 do:   [ :ifois |
     fenetre
           nextPutAll:(ifois printString);
           nextPutAll: ' fois ' ;
           nextPutAll: (self printString) ;
           nextPutAll: ' = ' ;
           nextPutAll:((ifois * self) printString);
          cr. ] .
 Menu message: 'Cliquez en gauche ici pour terminer'.
 fenetre close.
^nil

puis nous écrivons la demande du nombre et l'appel de cette méthode table dans la méthode tabmult de la classe Demo soit le texte

 tabmult  " Table de multiplication "
 | n  |
 n := Prompter prompt: 'Donner un entier de 1 à 10 ' default: '3'.
 ( n asInteger) table.

Après un premier essai de cette méthode tabmult, le cadrage des nombres n'est pas satisfaisant. Nous écrivons alors la méthode format dans la classe Integer soit le texte

format: unNombre
 " cadre sur le nb de caractères demandés"
 | s |
 s := self printString.
  [ (s size) > unNombre ] whileFalse: [
    s := ' ', s .
  ]. " fin de tant que "
 ^s

et nous modifions le texte de table en conséquence, à savoir les lignes

           nextPutAll:(ifois printString);

           nextPutAll:((ifois * self) printSting);

sont remplacées par

           nextPutAll:(ifois format:  5);

           nextPutAll:((ifois * self) format: 5);

Lors de l'évalution de Mademo tabmult on obtient alors le texte de la table de multiplication

sm1

puis le message de fin à l'écran

sm1

On appréciera le fait de pouvoir tester séparément les méthodes, par exemple en essayant successivement dans le Transcript les expressions


   5   format: 2

   5   table:

   Dgh tabmult

ce qui serait beaucoup plus compliqué à faire dans un langage compilé... On remarquera aussi que SmallTalk est résistant aux erreurs : si on essaie d'évaluer 3 format: '5' la boucle dans la méthode format est infinie, mais SmallTalk ne "plante" pas pour autant : il affiche une fenetre de "Debug" qui prévient qu'il y a un "Stack Overflow" c'est à dire un débordement... bien joué, SmallTalk ! On pourra aussi essayer de faire une faute de frappe comme par exemple écrire printSt au lieu de printString pour voir le déboggeur en action ce qui doit fournir quelque chose comme

sm1