I. Pre-requis

Pour appréhender ce tutorial, il est préférable de connaitre les bases de la programmation orientée objets. L'environnement utilisé sera Squeak qui est téléchargeable à l'adresse suivante pour Windows et Linux : http://ftp.squeak.org/current_stable/.
Il existe d'autres environnements, comme visualworks, mais Squeak est libre.

Pour appréhender ce tutorial, il est préférable de connaître les bases de la programmation orientée objets: l'héritage et le polymorphisme
Le Smalltalk est un langage faiblement typé et repose sur la notion de typage dynamique. Cela signifie que la classe de l'objet est connue uniquement à l'exécution. Ainsi, lors de l'exécution d'un message, la machine virtuelle va remonter l'arbre d'héritage pour retrouver la méthode associée et donc l'exécuter. La machine virtuelle repose donc sur ces deux notions. Il faut le garder à l'esprit !!!

II. Introduction

II-A. Smalltalk

Le Smalltalk est langage datant de 1972 pour sa première version. La version actuelle est le Smalltalk-80 (des années 80 :D). Contrairement à différents langages, il ne possède pas de point d'entrée spécifique. En effet, l'environnement correspond au point d'entrée directement. L'avantage d'une telle structure est que l'on peut exécuter une portion de code rapidement sans avoir à se soucier de la déclaration d'une fonction main, winmain, ...

Il faut préciser que le Smalltalk est un langage interprété, l'environnement de développement (sueak) dispose donc d'une machine virtuelle. Cela permet de "décompiler" le code rapidement et d'y naviguer plus facilement pour atteindre l'endroit qui a généré une exception. Ce qui s'avère très utile pour le « déboggage ».

Bien que le Smalltalk soit surtout utilisé pour effectuer de la recherche , son objectif principal est d'écrire rapidement et clairement un algorithme. Il est utilisé dans de grands projets par des entreprises de renoms tels que Thales, AMD, ... Le Smalltalk est un langage généraliste bien que Squeak soit très orienté multimédia. On peut évidemment faire des interfaces graphiques et des objets graphiques (comme par exemple les E-Toys) mais aussi de la synthèse vocale, du graphisme en 2D et 3D, ...

II-B. Avantages et inconvénients du Smalltalk

Le Smalltalk est langage simple à apprendre de part le peu d'éléments servant de syntaxe. De plus, peu de concepts sont nécessaires pour le maîtriser. Les règles fixes, de visibilité et de priorités, ainsi que uniformité des entités (tout est objet) ajoutent en simplicité au langage. Il s'agit aussi d'un langage puissant car il est extensible et l'environnement correspondant permet d'effectuer des navigations et du deboggage très facilement. De plus, le Smalltalk est un méta-langage car il se décrit lui-même et la notion d'image permet d'éviter les problèmes de fichiers et de chemins.
Le principal inconvénient du Smalltalk est le typage dynamique. En effet, cela peut dérouter le développeur débutant habitué à respecter le type d'une instance et à déclarer une variable en fonction de ce type. Le typage dynamique supprime cette déclaration et rend la tâche plus difficile en cas de maintenance.(ceci est un avis personnel :))

II-C. Des petits programmes ....

Les petits programmes suivants sont écrits en C. Leur objectif est de servir de fil conducteur pour mieux comprendre comment fonctionne le Smalltalk :

Programme n°1 : hello world
Sélectionnez

int main(int argc, char** argv)
{
	char *hello = "hello world\n";
	printf("%s", hello);	
}
Programme n°2 : une fonction de base
Sélectionnez

int cinqAuQuintuple()
{
	int result=5, i;
	for (i=0; i<4;i++)
	{
		result *= 5;
	}
	return result;
}
Programme n°3 : les collections
Sélectionnez

int main(...)
{
	int notes[6], notessup10[6], i, j=0;
	
	// Ajouts des éléments au tableaux
	notes[0] = 10; notes[1] = 5; notes[2] = 1; notes[3] = 7; notes[4] = 15; notes[5] = 19;
	
	// Affichage des notes
	for (i=0; i<6;i++)
	{
		printf("%d ", notes[i]);
	}
	
	// Récupération des notes strictement supérieures à 10
	for (i=0; i<6;i++)
	{
		if (notes[i] > 10)
		{
			notessup10[j] = notes[i];
			j++;
		}
	}
	
	// Affichage du nombre de notes strictement supérieures à 10
	printf("\nnbnotes > 10 = %d", j+1);
}

III. Syntaxe

III-A. Eléments syntaxiques

Voici la liste des éléments syntaxiques constituant le Smalltalk :

Element syntaxique Signification un petit exemple
$ représente un caractère $L représente le caractère 'L', $e représente le caractère 'e'
' ' représente une chaine de caractères 'hello world' est équivalent à "hello world" en C/C++/Java
# représente un symbole (equivalent à un identifiant unique)  
#() permet de définir un tableau à la compilation var := #(1,2,3)
équivaut à
int *var={1,2,3}
en C/C++
{ } permet de définir un tableau à l'exécution var := {1,2,3*4} équivaut à int *var= malloc(3*sizeof(int));
var[0] = 1; var[1] = 2; var[2] = 3*4;
en C
| | permet de déclarer des variables |un|
un := 1
équivaut à
int un=1;
en C/C++
:=, <- représente l'affectation, <- est spécifique à squeak |un|
un := 1
équivaut à
int un=1;
en C/C++
^ équivaut au return en C, Java, C++, ..., permet donc de renvoyer le résultat d'une fonction ou d'un bloc  
. correspond au séparateur d'expression var :=1.
var := var+1.
équivaut à
var = 1;
var = var+1;
en C/C++/Java
; permet d'envoyer une cascade de messages à un objet (permet d'éviter de répéter le noms de l'objet) objet message1; message2; message3.
équivaut à
objet.message1();
objet.message2();
objet.message3();
en C++/Java
[] permet de créer un block  
:x permet de créer une variable locale à un bloc  
| permet de séparer les déclarations de variables des expressions d'un bloc  
self, super mot
clefs liés à la notion d'héritage :self représente l'objet lui même (correspond à this en C++/java), super sa superclasse
 
nil mot-clef représentant une valeur nulle (NULL en C, null en C++/Java), permet de tester si une variable est initialisée ou non  
true, false true est une instance de la classe True, et false de la classe False. Permet de représenter des valeurs booléennes  
thisContext représente la pile d'exécution  


Maintenant que les éléments de syntaxes ont été présentés, les petits programmes 1 et 2 vont pouvoir être traduits en Smalltalk :

Programme n°1 : hello world
Sélectionnez

| hello |
hello := 'hello world'.
Transcript show:hello.

Comme vous pouvez le constatez la variable ''hello'' n'a pas de type à la déclaration. Son type est connu seulement à son affectation. De plus, aucune "console" n'est utilisée en Smalltalk. Son équivalent est le "Transcript" dont on verra comment l'afficher à la section V.

Programme n°2 : une fonction de base
Sélectionnez

|v|
v := 5
(1 to:4) do:
	[ v := v*5.].
^v.

Dans cette fonction, 3 notions ont été introduite : le bloc, la boucle for et le return. Un block connait son contexte et peut être comparé à des pointeurs sur fonctions en C. Il s'agit d'une entité qui contient du code à exécuter. Cette entité est soit passée en paramètre, soit exécutée ultérieurement. Le programme n°2 donnera donc :

Programme n°2 : une fonction de base
Sélectionnez

|v block|
block = [ v := v*5.]
v := 5.
(1 to:4) do:block.
^v.

Cela permet d'éviter de recopier plusieurs fois le même block.

III-B. Explications sur les messages/ Règles de priorités

III-B-1. Différents types de messages

Les messages Smalltalk se classent selon 3 types

  1. message unaire : il s'agit de message sans arguments. Il est articulié de la manière objetreceveur nommethode
  2. Quelques petits exemples de messages unaires
    Sélectionnez
    
    90 cos. "renvoie 0 -> objet receveur = 90"
    25 sqrt. "renvoie 25 -> objet receveur = 25"
    'salut' isEmpty. "renvoie false -> objet receveur = 'salut'"
    Object new. "permet de creer un objet -> objet receveur = Object"
    
  3. message binaire : il s'agit de l'utilisation des opérateurs classiques. Il est articulé de la manière objetreceveur nommethode argument A noter que le nom de la méthode est composé d'au maximum 2 caractères parmi les suivants : +, -, *, /, &, =, >, |, <, ~, et @. Une exception existe car -- n'est pas utilisé pour des raisons syntaxiques.
  4. Quelques petits exemples de messages binaires
    Sélectionnez
    
    3 == 3. "renvoie true -> objet receveur 3 (gauche)"
    4 + 7. "renvoi 11 -> objet receveur 4"
    
  5. message à mots clefs : il correspond à l'appel de méthode classique. Il est articulié de la manière objetreceveur nomméthode:arg1 suitenomméthode:arg2 ...
  6. Quelques petits exemples de messages à mot-clef
    Sélectionnez
    
    array at:1 put:'test'. "correspond à array[1] = 'test'"
    aCollection add:'test'. "ajoute 'test' à la collection"
    

III-B-2. Règles de priorités sur les messages

Trois règles régissent la priorité entre messages en Smalltalk :

  • Règle 1 : L'ordre d'exécution des messages est le suivant : tout d'abord les messages unaires, puis les messages binaires et enfin les messages à mot-clef
  • Règle 2 : Les messages entre parenthèses sont prioritaires
  • Règle 3 : Si tous les messages sont du même type et que aucune parenthèse n'est introduite, ils s'exécutent de la gauche vers la droite

Voici quelques exemples :

Quelques petits exemples de priorités entre messages
Sélectionnez

array at:1+3 put:4-5. 
" + et - sont des messages binaires, at:put: un message à mot-clefs et pas de parenthèse 
     -> règle 1 -> array[4] = -1"
     
1+3/4-6. 
"que des messages binaires et pas de parenthèse
     -> règle 3 -> 4/4-6 -> 1-6 -> -5, cela renvoie donc -5"
     
(1+3)/(4-6)
" que des messages binaires mais des parenthèses
	 -> règle 2 -> 4/(-2) -> -2, cela renvoie donc -2 !!!!!"

IV. Collections

IV-A. Généralités sur les collections

Les collections permettent de regrouper et de faciliter l'accès aux données. Elles héritent de la classe Collection. Voici l'arbre d'héritage des collections :

Image non disponible
Hierarchie des collections

IV-A-1. Collections ordonnées

Les collections ordonnées héritent de la classe SequenceableCollection. Cette classe est abstraite et permet l'accès à ses éléments par des indices numériques. On y distingue deux types de collections ordonnées :

  • les collections à ordre interne : seule la classe Interval en fait partie. L'ordre n'est pas spécifié, seul le pas et les valeurs de départ et d'arrivée sont spécifiés. Cette classe ne permet que de faire l'équivalent d'une boucle for :
  • Exemple d'une boucle for
    Sélectionnez
    
    "int i;
     for (i=0; i<5; i++)
     {
     	printf("ok ");
     }"
     
    |inter|
    inter := Interval from:0 to:5 by:1.
    inter do:[ :e | Transcript show:'ok '.].
    
  • les collections à ordre externe : il s'agit des classes héritant de OrderedCollection et de ArrayedCollection. Ces collections sont rangées en fonction des ajouts successifs (méthode at: put:, ....). Ces classes permettent de modéliser, les listes, les tableaux dynamiques, les piles, ...

IV-A-2. Collections non ordonnées

Les collections non ordonnées correspondent aux ensembles (classes Set et IdentitySet) ainsi qu'aux dictionnaires.
La classe Bag représente une collection qui contient des éléments qui peuvent apparaître plus d'une fois, dans n'importe quel ordre sans possibilité d'accès.
La classe Set représente un ensemble basique : un élément apparaît une fois dans la collection, à n'importe quel endroit.
La classe Dictionary représente une collection ensembliste, de type Set, associant une clé et une valeur (un dictionnaire).

IV-B. Messages spécifiques aux collections

Pour présenter les messages nous allons les diviser en 3 catégories. Commencons par les messages liées aux collections de taille variable (toutes sauf Array) :

  • add: unObjet : permet d'ajouter unObjet à la collection
  • addAll: autreCollection : permet d'ajouter tous les éléments de autreCollection à la collection
  • remove: unObjet ifAbsent: ["traitements"] : permet de supprimer unObjet de la collection et execute traitement si un objet est absent
  • remove: unObjet : permet de supprimer unObjet de la collection et provoque une erreur si un objet est absent
  • removeAll: autreCollection : permet de supprimer tous les éléments présents dans autreCollection de la collection

Passons maintenant aux messages liés aux collections ordonnées :

  • first : permet d'obtenir le premier élément de la collection
  • last : permet d'obtenir le dernier élément de la collection
  • at: indice : permet d'obtenir l'élément présent à l'indice indice
  • at: indice put: unObjet : permet d'ajouter unObjet à l'indice indice de la collection

Passons ensuite aux messages pour parcourir les collections :

  • do: [:element| "traitements"] : chaque élément est traité, par traitement, grâce à la variable elementle représentant
  • select: [:element| "expression booléenne"] : permet d'obtenir les éléments de la collection vérifiant l'expression booléenne
  • reject: [:element| "expression booléenne " ] : permet d'obtenir les éléments de la collection ne vérifiant pas l'expression booléenne
  • collect: [:element| "traitements"] : retourne une collection contenant les éléments dont on leur a appliqué le traitement
  • detect: [:element | "expression booléenne"] ifNone: ["traitements alternatifs"] : retourne le premier élément vérifiant l'expression booléenne. Si aucun élément n'est trouvé le traitement alternatif est exécuté
  • detect: [:element | "expression booléenne"] : retourne le premier élément vérifiant l'expression booléenne. Si aucun élément n'est trouvé une erreur est lancée.

Passons enfin aux messages spécifiques aux dictionnaires (classes héritant de Dictionary):

  • at: clé put: valeur : insère valeur à la case référencée par clef
  • at: clé : permet d'obtenir la valeur de la case référencée par clef mais provoque une erreur si clef est absente du dictionnaire
  • at: clé ifAbsent: ["traitements"] : permet d'obtenir la valeur de la case référencée par clef et exécute traitements si clef est absente du dictionnaire
  • removeKey: clé : retire la clef et la valeur référencée du dictionnaire
  • keysAndValuesDo: [:clé :valeur| "traitements"] : exécute traitements pour chaque couple clef/valeur
  • keysDo: [:clé | "traitements"] : exécute traitements pour chaque clef
  • do: [:valeur| "traitements"] : exécute traitements pour chaque valeur
  • collect: [:valeur| "traitements"] : retourne un ensemble (Set) contenant les valeurs traitées par traitements

IV-C. Exemple d'utilisation d'une collection

L'exemple qui suit correspond à la traduction du programme n°3 en Smalltalk :

programme n°3 : les collections
Sélectionnez

| notes notesup10 |
notes := OrderedCollection new.

" Ajout des notes au tableau "
notes add:10; add:5; add:1; add:7; add:15; add:19.

" Affichage des notes "
notes do:[:e| Transcript show:e; show:' '.].

" Récupération des notes strictement supérieures à 10 "
notesup10 := notes select:[ :e | e > 10].

" Affichage du nombre de notes strictement supérieures à 10 "
Transcript show: Character cr; show:'nb de notes > 10 = '; show:(notesup10 size).


Comme on peut le constater, la notion de collections présente moins de restrictions qu'un tableau classique : la taille n'est pas limitée et la taille est connue. De plus un des gros avantages, est que l'on accède directement à l'élément sans passer par une indexation ce qui empêche d'effectuer des débordements mémoire par exemple.
De plus, un des gros avantages du Smalltalk est mis en avant : la faible densité du code.

V. Utilisation basique de squeak

Maintenant je vais vous expliquer comment utiliser Squeak pour pouvoir apprendre la syntaxe Smalltalk. D'autres tutoriaux viendront plus tard pour vous apprendre à faire de la programmation orientée objets. Une petite indication : pour effectuer des copier/coller les raccourcis se font grâce à la touche ALT et non CTRL. Pour commencer, voici l'interface de Squeak au démarrage :

Image non disponible

Maintenant, nous allons ouvrir le transcript. Pour cela, faites clic gauche(sous windows) et cliquez sur open dans le menu suivant :

Image non disponible

Ensuite choisissez "Transcript" dans le menu open :

Image non disponible

Maintenant vous êtes dans le Transcript. Vous pouvez saisir du code qui pourra être exécuté. Pour cela, vous saisissez le code puis vous passez 2 lignes pour la lisibilité si un affichage doit être fait.

Image non disponible

Maintenant vous avez 2 solutions possibles : soit vous faites un "Do it" comme spécifié dans l'image suivante. Ce cas est plutot à utiliser si un affichage dans le transcript doi être effectué. L'autre cas est de faire un "inspect it" qui permet de voir les valeurs composants le dernier objet manipuler. (Petite astuce : si vous voulez voir un objet précis, saisissez le nom de l'objet à la fin du code à exécuter). Pour ces 2 méthodes, il faut sélectionner le code à exécuter, comme dans l'image suivante :

Image non disponible

Et enfin, voici le résultat affiché :

Image non disponible

VI. Conclusion

En conclusion, vous êtes maintenant capable de faire de petites procédures en Smalltalk et dans Squeak. Dans le prochain article, on verra plus en détails l'environnement Squeak et la programmation orientée objets dans Squeak.

VII. Remerciements

Je tiens à remercier Miles, pcaboche, gorgonite, nico-pyright(c) et Gege2061 pour leur aide et leur relectures. Je remercie plus particulièrement Farscape pour sa patience, son aide et tout ce qu'il a fait.