Pascal, Objets et Smalltalk

Nous essaierons de présnter ici ce qu'il manque au Turbo Pascal pour être parfait, ce que représente le Turbo Pascal Objet, ce qu'est un langage objet complet à travers Smalltalk. Plus que le côté technique des notions, c'est la philosophie de la programmation mise en jeu qui sera discutée. On pourra ainsi déduire ce que peut être une surcouche objet associée à Lisp, Prolog, C etc. Les exemples seront donc simples et souvent limités à des entrées/sorties.

1. Ce qu'il manque au Pascal

1.1 Le renvoi de plusieurs valeurs par une fonction

Par exemple on ne peut pas écrire :
   type tablo = array[1..10] of byte ;
   function f(t : tablo) : tablo...
On aurait pourtant pu imaginer qu'on renvoie des valeurs avec la même syntaxe que le passage des paramètres ; ainsi l'écriture suivante non plus n'est pas possible :
   function minmax(x,y : integer) : integer,integer ; begin
      if x < y   then minmax := x,y
            else minmax := y,x
   end ;

1.2 La surcharge explicite des noms de modules

On peut écrire readln pour un integer ou un real mais on ne peut pas écrire readln(X) avec X une variable de type vecteur ou tableau, même si vecteur et tableau sont des types définis par l'utilisateur.

1.3 La surcharge des opérateurs

On ne peut définir +(,). Et pourtant, en interne, + a déja plusieurs significations, comme dans 2+4 et 'a'+'nimal'.

1.4 La gestion modulaire

Cette gestion par fichier inclus, modules pré-compilés (unités) est lourde et inadaptée à un développement modulaire : charger toute une unité pour une seule procédure (crt pour clrscr par exemple) est lourd. Quand on extrait un module d'un fichier inclus et qu'on le modifie pour un nouveau programme, on se retrouve avec deux versions ressemblantes d'un même module. Le réinjecter dans le fichier originel après modification obligerait à revérifier tous les programmes utilisant ce fichier...

1.5 Les expressions régulières

On peut écrire if c in ['o','n'] mais pas if c ~ [A-Z][A-Z0-9]* qui signifierait si c correspond à une lettre suivie d'un nombre quelconque de lettres ou chiffres.

1.6 Les tableaux associatifs (ou l'indexation généralisée)

Par exemple T["oui"] ne peut être utilisé. Pourtant, ce genre d'indexation simplifie énormément les recherches, les comptages.

2. Vers le Turbo Pascal Objet

2.1 Exemple de la surcharge

Nous nous limiterons dans un premier temps à la notion de surcharge des noms de modules (procédures ou fonctions). Prenons comme exemple celui de la lecture et de l'écriture de trois nombres : nbe (un entier), nbr (un réel) et nbf (une fraction). Un exemple plus concret pourrait être celui de la lecture et de l'écriture de trois nombres : k (un nombre de kilos), p (un prix), n (un numéro de facture) mais pour des raisons exposées plus loin nous préférons nbe, nbr et nbf. Prenons un algorithme simple de demande de nbe et nbr :
{ Demande de nbe et nbr }
écrire " donner un entier "
lire nbe
écrire " et aussi un réeel "
lire nbr.
Le typage des variables nbe et nbr est (humainement) explicite d'après le contexte et les messages de demande. Si ce n'avait pas été le cas, fidèles à nos principes, des indications de typage auraient été mises en commentaires. Il est facile d'écrire un programme Pascal traditionnel qui l'exécute. Par contre, si on rajoute la demande de nbf, on tombe sur le problème de la lecture des deux éléments de nbf, à savoir son numérateur et son dénominateur. Aucun lecteur n'a de mal à comprendre l'algorithme
{ Demande de nbe, nbr et nbf }
écrire " donner un entier "
lire nbe
écrire " donner aussi un réeel "
lire nbr
écrire " et enfin une fraction "
lire nbf.
La traduction en Pascal classique est délicate. Après l'introduction du type fraction, soit on utilise une procédure LitFraction soit on écrit directement la lecture du numérateur et du dénominateur. Les deux programmes correspondants ne sont donc pas homogènes : soit readln n'apparait plus explicitement pour lire nbf, soit il apparait deux fois (une pour chaque élément de nbf). On trouve ci-après les programmes associés avec les deux implémentations possibles du type fraction.
program Un ; const num = 1 ; den = 2 ;
type   entier = integer ; réel = real ;
      fraction = array[num..den] of entier ;
(*$I Fraction.Inc *) {qui contient la procédure LitFraction} var nbe :
entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
   writeln('donner un entier ') ;
   readln ( nbe ) ;
   writeln('donner aussi un réeel ') ;
   readln ( nbr ) ;
   writeln('et enfin une fraction ') ;
   LitFraction( nbf )
end.

program Deux ;
type   entier = integer ; réel = real ;
      fraction = record num,den : entier end ;
var nbe : entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
   writeln('donner un entier ') ;
   readln ( nbe ) ;
   writeln('donner aussi un réel ') ;
   readln ( nbr ) ;
   write('et enfin une fraction ') ;
   write('(numérateur puis  et dénominateur puis ) ') ;
   readln( nbf.num );
   readln( nbf.den )
end.
Pour garder l'homogénéité et la correspondance avec l'algorithmique, on peut inventer un code de lecture : 1 signifie entier, 2 signifie réel, 3 signifie fraction. On obtient alors
program Trois ;
(*$I Fraction.Inc *) {qui contient la définition de Fraction}
type entier = integer ; réel = real ; var nbe, nbr, nbf : nombre ;
(*$I Lecture.Inc *)  {qui contient la procédure Lit}
begin { Demande de nbe, nbr et nbf }
   writeln('donner un entier ') ;
   Lit( nbe , 1 ) ;
   writeln('donner aussi un réel ') ;
   Lit( nbr , 2 ) ;
   writeln('et enfin une fraction ') ;
   Lit( nbf , 3 )
end.
Pour que la procédure Lit puisse être déclarée, il faut regrouper les trois types entier, réel et fraction sous un même type commun. On peut utiliser un enregistrement comme
type nombre = record e : entier ; r : réel ; f : fraction end ;
ou même, si on ne veut pas perdre de place, utiliser le code de lecture :
type nombre = record case typ : byte of 1 : (e : entier) ; 2 : (r : réel)
; 3 : (f : fraction) end { de case et de record }
Ce type d'écriture n'est pas satisfaisant. En effet, il faut réécrire l'algorithme en explicitant le numéro de code de lecture, ce qui ne se justifie pas en algorithmique.

2.2 Surcharge et héritage

Regardons maintenant comment on peut résoudre ce problème en termes d'objets. Inventons une classe nommée Nombre avec trois sous-classes : entier, réel et fraction. Cela s'écrit en Pascal Objet :
type   Nombre   = Object ...       end
      Entier   = Object( Nombre ) end
      Réel     = Object( Nombre ) end
      Fraction = Object( Nombre ) end.
Mais en objet, les données et les actions sont associées. La définition complète est donc :
type Nombre   = Object valeur : real ; (* même pour un entier *)
               procedure Lire end
       Entier   = Object( Nombre ) end
     Réel     = Object( Nombre ) end
     Fraction = Object( Nombre )
                num, den : integer ; (* quand même ! *)
                procedure Lire end

procedure Nombre.Lire ; begin
   write(' Entrez un nombre ') ;
   readln(Valeur)
end ;

procedure Fraction.Lire ; begin
   write(' Entrez le numérateur ') ;
   readln(Num) ;
   write(' Entrez le dénominateur ') ;
   readln(Den) ;
   Valeur := Num / Den
end ;
Une remarque s'impose : la surcharge de Lire pour un entier et un réel est remplacée par l'héritage (ou transmission) de la procédure Lire pour un objet. Par contre, si on voulait vraiment que les objets entiers aient vraiment le type entier, il faudrait redéfinir Entier.Lire. Le programme principal associé à de telles déclarations est
program Quatre ;
   {... ici les déclarations d'objet précédentes }
   var nbe : entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
   writeln('donner un entier ') ;
   nbe.Lire ;
   writeln('donner aussi un réeel ') ;
   nbr.Lire ;
   writeln('et enfin une fraction ') ;
   nbf.Lire
end.

3. PROGRAMMATION (ORIENTEE) OBJETS

3.1. Présentation générale

Styles de programmation, modes de programmation ou simplement mode des années 90, la Programmation Objet (PO) et la Programmation Orientée Objet (POO) sont très présentes dans l'informatique, que ce soit sous forme de logiciels, de livres ou simplement de sujet à discussion. Nous essaierons ici de présenter la notion d'objet et d'éclairer les différences entre PO et POO. Pour cela, au travers du problème classique de Comar on se servira de divers langages représentatifs des différentes tendances.

3.2. Notion d'Objets

En programmation classique, on compare souvent les algorithmes à des recettes de cuisine pour bien montrer l'aspect "déroulemental", la "technique manipulatoire" du travail à effectuer. Pour continuer l'analogie, de même qu'une recette consiste en une liste d'ingrédients et en une méthode de préparation, un objet est composé de données et d'actions liées à ces objets. Par exemple, une disquette peut être identifiée par ses caractéristiques (taille,secteurs etc.) et les fonctions qu'elle supporte (lire, écrire etc.). Cette description est plus un modèle de disquette qu'une disquette réelle et en ce sens, on parlera plutôt de la "classe" des disquettes plutôt que de l'objet "théorique" nommé disquette. Quatre notions sont fondamentales en PO : l'encapsulation, l'héritage et le polymorphisme et l'envoi de messages. L'encapsulation est l'intégration des différentes parties de l'objet (données et programmes, structure et opérations, champs et méthodes, caractéristiques et opérations) dans un même ensemble tout en masquant le détail de l'écriture, comme dans le cas de bibliothèques de programmes précompilées. Un objet devient alors une "boite noire" avec qui on ne communique que par message. L'héritage permet transférer des propriétés ou des actions d'une classe à une sous-classe spécialisée. Ainsi, la sous-classe Ecrans issue de la classe Périphériques_de_Sorties hérite de l'action Initialisation. Cet héritage définit une structuration à la fois des données en elles- mêmes et des données entre elles, ce que ne fait pas la programmation classique. Au delà du regroupement et de la cohérence des sous programmes, cela donne une architecture des donnés comparable à l'architecture des programmes. Cela permet en particulier de retrouver rapidement tout ce qui concerne un même type de donnée (les entiers, les fichiers, les graphes etc.). Le polymorphisme est le regroupement sous le même nom d'actions différentes liées aux objets. Ainsi les sous-objets de la classe Polygones (tels triangles, rectangles etc.) ont la même méthode Périmètre dont la définition est différente suivant l'objet. Associé à l'héritage, le polymorphisme assure une homogénéité quant aux actions, un masquage de la définition. Le polymorphisme dont un exemple simple est la surcharge des opérateurs classiques (comme + qui peut désigner l'addition de nombres, de fonctions, de vecteurs... et la concaténation des chaînes de caractères) permet une plus grande lisibilité et un meilleur niveau d'abstraction. L'envoi de messages se substitue à l'écriture d'expression, l'appel de sous programmes. Les instructions traditionnelles argument_gauche opérateurO argument_droiteet appeldu sousprogramme X avec l'argument A deviennent respectivement l'envoi du message opérateurO argument_droite à l'objet argument_gauche et l'envoi du message X à l'objet A. Bien qu'artificiel parfois (comment penser que dans 2 + 3 les nombres 2 et 3 ne jouent pas un rôle symétrique mais qu'il y un le message + 3 envoyé à 2 ?) ce mécanisme de transmission de message est très souple.

3.3. Conception par objets

De même de la programation modulaire, le découpage en fichiers logiques et physiques (fonction, procédures,sous programmes, et fichiers inclus) représentent un progrès par rapport à la programmation monobloc, la programmation par objets permet un meilleur niveau d'abstraction. L'abstraction provient tout d'abord d'une simplification ou unification : au lieu de manipuler des données et des programmes, on ne traite qu'un seul type d'entité, les objets. Ensuite, il y a un seul moyen de communication : les messages. Enfin, le raffinement progressif dans l'élaboration des solutions se font par l'écriture de sous-classes ou de sur-classes. La conception d'une application orientée objet repose sur la construction d'une hiérarchie d'objets exprimant la "généalogie" des objets de l'application. On se méfiera, toutefois de la dépendance du langage sous-jacent : en PO, la gestion des objets diffère beaucoup de celle de la POO. Ainsi, un objet pour Turbo Pascal Objet ressemble à une structure de type ENREGISTREMENT, qui n'est jamais qu'une façon parmi tant d'autres d'implémenter les objets. Le schéma de développement en programmation classique passe par une phase d'analyse où les actions sont privilégiées. Les données et les structures de données ne sont intégrées qu'en tout dernier. Ainsi, l'algorithme focalise la lecture d'un nombre alors que l'implémentation et la traduction dans un langage typé viendra spécifier s'il s'agit d'un entier, d'un réel etc. même s'il est difficile de justifier à priori le choix (comment choisir entre le type real, single et complex en Turbo Pascal ?). En PO, on se concentre d'abord sur les classes d'objets, puis sur leurs caractéristiques et enfin sur les actions. Cette programmation permet de mélanger plus intimement algorithme et données, d'examiner les rapports entre code et valeur. On viendra par exemple dans le cas de saisies multiples recenser les différents types d'objets à saisir, choisir quelles vérifications on imposera avant de détailler chaque classe.

4. Exemple de PO pour le problème de Comar

4.1. Vocabulaire de la PO

Le vocabulaire traditionnel de la PO comprend les termes de Classes, d'Objets, de Champs, de Méthodes, de Messages et d'Instances. Ces différentes notions peuvent être présentées dans la résolution en PO du problème de COMAR. Une classe est la définition "théorique" de l'objet, son moule, sa "déclaration" au sens d'une énumération de ses possibilités. Ainsi la classe Phrase est caractérisée par sa longueur, son texte. Les "opérations" sur Phrase sont la lecture, la noelisation... Les champs d'un objet sont les données associées, les méthodes sont les programmes associés. Ainsi longueur et texte sont des champs de Phrase, lecture et noelisation des méthodes de Phrase (mais on aurait pu définir longueur comme une méthode). Les objets comuniquent entre eux et avec le monde extérieur par message. L'envoi d'un message est donc similaire à l'appel d'une procédure, d'une fonction. Une instance de la classe Phrase est la réalisation effective d'un objet de type Phrase. La création d'une instance est différente du remplissage des champs de cette instance qui se fait soit par héritage soit par messages.

4.2. Application au problème de Comar

Une instance de la classe Phrase est la réalisation effective d'un objet de type Phrase. La création d'une instance peut être différente du remplissage des champs de cette instance qui se fait soit par héritage soit par messages. Ainsi, le remplissage des chamlps longueur et texte d'un objet Phrase sera certainement réalisé par la méthode Lecture. Dans le problème de Comar, la donnée de départ est une PHRASE composée d'un ensemble de MOTS qu'il faut DENOMBRER, INVERSER et qui doit ensuite être RETRANSCRITE en arbre de noël avec pour sommet son MILIEU. A priori, deux OBJETS émergent immédiatement : l'objet PHRASE et l'objet MOT qui héritera de ses CHAMPS et de ses METHODES. L'objet PHRASE aura notamment pour CHAMPS sa longueur (nombre de caractères), sa cardinalité (nombre de mots) et pour METHODES la lecture, la recherche du milieu, la "Noëlisation" (écriture en arbre de noel), l'inversion (retournement). L'objet MOT héritera de longueur, de lecture et d'inverse. Si on préfere, on puet ne créer qu'un objet (Chaine) dont Phrase et Mot sont des instances, car finalement, un mot peut être considérée comme une phrase. On peut généraliser le problème de Comar en demandant d'afficher en arbre de Noël soit une phrase, soit un mot, soit un vecteur (liste de nombres) ou une liste de symboles Dans tous ces cas, on doit décomposer une collection en éléments et la réécrire en arbre de noel. On peut donc créer une sur-classe de Phrase nommée Collection dont les champs sont sa cardinalité (nombre d'éléments) et ses éléments, dont des méthodes sont lecture, noelisation, inversion. C'est à ce niveau que la PO prend tout son sens : le temps de développement passé à rajouter une nouvelle collection est très réduit puisqu'on n'aura pas à rechercher les sources, à réécrire ou à recopier en modifiant légèrement les entrées, les sorties. Une présentation simplifiée de la hiérarchie des objets correspond à
Collection
    Phrase
       Mot
    Vecteur
       Nombre
         Entier
         Rationnel
...
    Liste
       Symbole
etc.
Le graphe d'héritage complet associé est alors :
    Objet Collection Champ   Cardinalité
    +                Champ   Eléments
    +                Méthode Lecture
    +                Méthode Noelisation
    +                Méthode Inversion
    +-----Objet Phrase Champ   Cardinalité (hérité)
    +     +            Champ   Eléments (hérité)
    +     +            Champ   Longueur (propre)
    +     +            Méthode Lecture (héritée)
    +     +            Méthode Noelisation (héritée)
    +     +            Méthode Inversion (héritée)
    +     +-----Objet Mots Champ   Longueur (hérité)
    +                      Champ   Contexte (propre)
    +                      Méthode Lecture (hérité)
    +-----Objet Vecteur Champ   Cardinalité (hérité)
    +     +             Champ   Eléments (hérité)
    +     +             Méthode Lecture (héritée)
    +     +             Méthode Noelisation
    +     +             Méthode Inversion
    +     +-----Objet Nombre Champ   Valeur (propre)
    +                        Champ   Signe (propre)
    +                        Méthode Lecture (héritée)
    +-----Objet Liste Champ   Cardinalité (hérité)
    +     +           Champ   Eléments (hérité)
    +     +           Méthode Lecture (héritée)
    +     +           Méthode Noelisation
    +     +-----Objet Symbole Champ Valeur (propre)
    +                        Méthode Lecture (héritée)
etc.
Le degré d'abstraction et de raffinement introduit par la PO est clair : au lieu de définir de nombreuses structures de données similaires (comme en Pascal) et de réécrire les procédures associées, la PO permet de partir d'un type de structure et de ses procédures pour ensuite dériver d'autres structures, que ce soit des sous-structures ou des sur-structures. Concrètement, cela rejoint la technique de "refinement" (raffinement) de certains types de programmation. Au fur et à mesure que l'on programme, on crée ainsi des hiérarchies d'objets. De plus, et la cohérence (ou seulement la liaison de par le même traitement) entre Phrase, Vecteur, Liste se retrouve grâce à la clase Collection. Le Pascal classique, ne le permet pas. Ainsi, pour afficher une liste de caractères un par un stockés dans un tableau de caractères nommé T de taille n , on utilise la boucle for k := 1 to n do begin writeln( T[k) end ; de même, pour afficher les différents éléments d'un tableau X on écrit for k := 1 to n do begin writeln( X[k) end ;sans qu'il soit possible de regrouper ces instructions en une seule, à cause de la nature des termes utilisés. Même si ces instructions sont courtes, le même problème se pose pour des procédures. Ainsi, le tri d'une liste de nombres et le tri d'une liste de mots ne peut se faire en Pascal avec la même procédure, alors que la seule différence se situe au niveau de la déclaration de l'objet trié ! En PO, l'héritage permet d'utiliser une métthode pour diféfrents objets, puisque la notion de type n'est pas explicité. Si toutefois on le désire, on peut redéfinir à l'intérieur d'une classe une méthode si l'héritage n'est pas suffisant. Par exemple, pour un problème de Comar général, on pourrait envisager un graphe d'héritage bâti sur la notion de collection.

4.3. Intérêts et difficultés de la PO

Ce type de programmation n'est pas pour autant plus facile, plus naturel que la programmation traditionnelle. Tout d'abord il faut être capable de structurer la hiérarchie, savoir faire dériver, hériter correctement. La qualité du logiciel associé est lié à une non redondance, un masquage des procédures les plus internes. Avant d'inventer un objet, il faut donc vérifier qu'il n'a pas déjà été inventé, comme en programmation classique. Avec un langage comme Smalltalk, il y a en standard environ 100 objets et 2000 méthodes. S'y retrouver représente un gros travail. Par exemple, il serait incohérent de réinventer le calcul de la moyenne d'une liste de nombres si elle existe déjà. Parcourir l'encyclopédie des classes (partie 4 du manuel de Smalltalk) où utiliser en interactif le Class Hierarchy Browser prend beaucoup plus de temps que d'écrire la méthode nommée moyenne pour l'objet liste de nombre. Le masquage des procédures donne une programmation plus aisée : on travaille comme avec une bibliothèque de sous programmes dont on ne connait que la syntaxe d'appel, les conditions d'utilisations, mais pas le texte source. Cela ne garantit pas pour autant de la "bonne" écriture de ces procédures. Un mauvais programmeur fera un piètre programmes en PO. Prenons un exemple concret : supposons que nous redéfinissions la conversion en majuscule d'une chaine de caractères par programme, sachant qu'on dispose d'une conversion caractère par caractère seulement. Nous voulons utiliser pour cela une boucle REPETER ... JUSQU'A. Notre idée (fausse) de départ est qu'on doit traiter les caractères 1 à n où n désigne la longueur de la chaine. Pour une chaine nommée CH l'algorithme itératif associé est
N * longueur(CH)
{ on parcourt la chaine caractère par caractère
  noté K et repéré par son indice IC }
IC * 1
répéter
   K * SousElément(CH,IC)
   ... { conversion de K dans CH }
   IC * IC + 1
jusqu'à IC = N
L'erreur de notre méthode est de ne pas prendre en compte la chaîne vide. Faire de notre algorithme une fonction ou une procédure ne résoudra pas le problème, pas plus que d'en faire une méthode pour les objets associés aux chaines de caractères. Une autre difficulté de la PO qui ne la rend pas immédiatement accessible est qu'elle ne prend tout son sens sert que pour des "gros" programmes. Pour un programme court et jetable (et il y en a beaucoup), la PO n'est pas forcément rentable. Par contre, des environnements complets (tels Smalltalk) présentent un "plus" par rapport à des langages orientés objets : la diminution du nombre de fichiers, par exemple, est flagrante. Smalltalk contient tout en une seule "image". On est loin des fichiers sources, des fichiers inclus, des fichiers de données, des fichiers de code relogeable, des fichiers de code exécutable etc.

4.4 Langages :

Quatre langages différents viendront détailler les différences entre PO et POO, à savoir : Smalltalk, Lisp/Flavors, Turbo Pascal Objet et C++. Plus qu'un exercie de style ou d'école, le passage par ces divers langages viendra montrer les pièges et les oppositions de concepts, de développement. On s'attachera donc, après l'écriture des programmes à une comparaison critique. SMALLTALK est le langage objet par excellence car tout n'y est qu'objet. Smalltalk est écrit en Smalltalk (sauf le noyau écrit en assembleur). Il intègre les objets et leur environnement dans un tout homogène et graphique. Le passage par ce langage de référence est obligé. LISP/FLAVORS est aussi un standard. Si la PO et l'IA sont liés, ce langage en montrera les liaisons, les apports réciproques. TURBO PASCAL Orienté Objets est dérivé de Pascal, langage très connu en milieu universitaire. L'utiliser permettra de dégager les bienfaits ou les problèmes de la POO. C++ est une référence en termes de développement industriel (ou au moins posé comme tel).

5. Le langage Smalltalk

Un exemple simple de programme Smalltalk est celui qui correspond à un dialogue de "bonjour". Le programme affiche Bonjour sur l'écran, puis demande un prénom. Il affiche ensuite sur la ligne suivante la date et l'heure puis écrit "au revoir" suivi du nom de la personne en majuscules. Exemple d'exécution : si monsieur TEST lance le programme à 11 h 30 : 12 le 12/01/1992, on aura sur l'écran : Bonjour Quel est votre prénom ? Test Le 12 janvier 1992 à 11:30 ; au revoir, TEST. L'algorithme correspondant ne présente aucune difficulté. Il fait référence aux fonctions date, heure et maju. On aurait pu écrire date() et heure() au lieu de date et heure, MCRIRE au lieu de MECRIRE (qui signifie, rappelons "écrire sur la même ligne sans passer à la ligne suivante") pour garder autant de lettres que dans le mot ECRIRE.
{ Algorithme de BONJOUR }
 ECRIRE  "Bonjour."
 MECRIRE "Quel est votre prénom ?
 "  LIRE    pren
 ECRIRE  "Le ",date," à ",heure," au revoir, ",maju(pren)
L'exemple un met en jeu la notion d'entrée et de sortie, de conversion en majuscules, de récupération de la date et de l'heure. En Smalltalk, on écrit :
 bonjour  " Exemple de commentaire, lire, écrire et affectation "
 | pren |
  Cursor offset: 140 @ 150.
  pren := Prompter   prompt: ' Bonjour. Quel est ton prénom ? '
                     default: ''.
  pren := ' Merci. Au revoir ' ,
             (pren asUpperCase), '' ,
             (Date today) printString.
  Cursor offset: 140 @ 300.
  Menu message: pren.
  " fin de bonjour "
Ce programme appelle quelques commentaires. Les programmes sont écrits en format libre, mais avec respect des majuscules et minuscules pour les identificateurs. Les instructions sont séparées par des points (et l'indentation les met en évidence). Les variables en majuscules correspondent à des variables globales ; certaines méthodes ont un : en fin pour indiquer qu'il faut des paramètres ; pren est une variable locale (indiqué par les |). Ainsi Cursor, Prompter, Date, Menu sont des variables globales, offset:, prompt:, default:, asUpperCase, today, printString, message: sont des méthodes. Smalltalk travaille uniquement en mode graphique fenêtré. Pour avoir le même affichage à chaque exécution, on indique une position curseur (sinon, l'affichage est effectué en fonction de la position courante de la souris). La demande de pren est accompagné d'une valeur par défaut, ce qui est une bonne chose. ECRIRE puis MCRIRE de l'algorithme ne sont pas traduits. Il est en effet difficile de réécrire de façon simple (c'est-à-dire sans passer par les objets d'entrées:sorties de type "stream") à la suite du dernier texte écrit. La fin du programme par Menu permet d'attendre une réponse utilisateur. Sinon, comme en Pascal, le programme se termine et l'affichage disparait. Pour sauvegarder ce programme (qu'on a pu écrire dans le Transcript, par exemple), il faut l'associer à un objet. Ici, par exemple, on pourra en faire une méthode d'un objet "démonstration". Le nom de la méthode sera bonjour. On mettra donc en en-tête :
bonjour  " Exemple de commentaire, lire, écrire et affectation "
Les commentaires entre " sont facultatifs. Une fois l'ensemble du texte sélectionné, on appelle le "Browser" qui gère les objets et leur métodes. On se positionne sur la classe Démo et utilise l'option File du menu. Pour réutiliser la méthode Bonjour, dans le Transcript, on ne peut taper bonjour puis l'exécuter car en objet, tout message a un objet récepteur (seuls les paramètre sont facultatifs). Il faut donc créer un objet de type démo (ou en retrouver un). Dans le premier cas, on exécute
 | tstDemo |
 tstDemo := DemoClass new.
 tstDemo bonjour
Dans le second cas, on cherche avec le dictionnaire (en exécutant Smalltalk inspect et en descendant dans la liste des variables) un objet global de type demo. On trouve par exemple Demo DemoGil Gil. On peut alors exécuter
 Demo bonjour
Pour connaitre la classe d'un objet, par exemple pour savoir comment 2 est reconnu, il suffit de taper
2 class
Le système répond alors : SmallInteger. De même, SmallTalk est de type SystemDictionnary. On peut parcourir une classe par edit et une variable par inspect. Ainsi, on peut taper
 2 inspect
 Smalltalk inspect
 DemoClass edit.
Comme deuxième exemple, prenons la table de multiplication. L'algorithme est :
ECRIRE " De quel nombre voulez-vous la table ? "
LIRE nb
ECRIRE " Table de multiplication de " , nb
POUR indr  DE1A 10
   produit <-- indr  * nb
   ECRIRE indr , " fois " nb , " = " , produit
FIN_POUR indr { DE1A 10 }
L'écriture en Smalltalk se fait en plusieurs temps : on écrit le module principal comme une méthode de l'objet démo, soit le programme :
tabmult  " Table de multiplication "
   | nb  |
   nb := Prompter prompt: 'Donner un entier de 1 à 10 '
                  default: '3'.
(nb asInteger) table.!
" fin de tabmult "
Ce programme fait appel au module table. Celui-ci est une méthode associée à un objet de type integer. On peut donc exécuter dans le Transcript, indépendamment du programme précédent l'instruction "4 table". Le détail de table suit.
table  " de multiplication "
   | fenetre  |
   fenetre := TextEditor windowLabeled: 'Table de Multiplication '
             frame: (210 @ 30 extent: 400 @(Display extent //2)y).
 1 to: 10 do: [ :indr | fenetre nextPutAll: indr format ;
                 nextPutAll: ' fois ' ;
                 nextPutAll: self printString ;
                 nextPutAll: ' = ' ;
                 nextPutAll: (indr * self) format ;
                 cr.
               ].
fenetre activate.
Menu message: 'Cliquez en gauche ici pour terminer'.
" fin de table "
La méthode table utilise une méthode nommée format. Nous avons volontairement compliqué format puisqu'elle apelle format: (ceci pour montrer le passage de paramètres et la variable self). Le symbole ^ indique que la méthode renvoie le résultat de l'instruction qui suit le symbole ^ (à l'esclusion des commentaires).
format " Formate sur 3 caractères un entier "
^ self format: 3 " fin de format "

format: nbdeCar  " Formate un entier sur nbdeCar caractères  "
   | chaine |
   chaine :=  self printString.
   1 to: (nbdeCar - (chaine size)) do: [ :ind | chaine := ' ' , chaine ].
  ^chaine " fin de format: "
Il faut remarquer que format, format: peuvent être utilisées par n'importe quel programme, n'importe quelle méthode. Les méthodes sont donc des modules dynamiques, par raport aux modules des bibliothèques classiques. De plus, elles ne sont pas liées à un fichier particulier : elles font partie de Smalltalk. L'ensemble de Smalltalk est donc ouvert et sauvegardé dans un fichier image (qui est donc gros - de l'ordre du Méga). Le Transcript aussi et toutes les interactions sont aussi sauvegardées, ce qui permet de défaire tout ce qui a été fait, de retrouver des essais, des valeurs... Il est hors de question de présenter les 100 objets et 2000 méthodes (environ) disponibles en standard avec Smalltalk.