Introduction à Smalltalk

hiko-seijuro

Cette introduction a pour objectif de vous présenter les bases du Smalltalk. ♪

- Titre : Introduction à Smalltalk

- Auteur : hiko-seijuro

- Parution : 14 juin 2006

- Mise à jour : 28 septembre 2006

- Licence : Copyright ® 2006 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Prérequis

- II. Introduction

- II-A. Smalltalk

- II-B. Avantages et inconvénients du Smalltalk

- II-C. Des petits programmes…

- III. Syntaxe

- III-A. Éléments syntaxiques

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

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

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

- IV. Collections

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

- IV-A-1. Collections ordonnées

- IV-A-2. Collections non ordonnées

- IV-B. Messages spécifiques aux collections

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

- V. Utilisation basique de squeak

- VI. Conclusion

- VII. Remerciements

I - Prérequis

Pour appréhender ce tutoriel, il est préférable de connaitre les bases de la programmation orientée objet. 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 tutoriel, il est préférable de connaitre les bases de la programmation orientée objet : 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ébogage ».

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 renom telles 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 (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 par 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 qu’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 débogage très facilement. De plus, le Smalltalk est un métalangage, 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

int main(int argc, char** argv)
{
char *hello = "hello world\n";
printf("%s", hello);
}

Programme n° 2 : une fonction de base

int cinqAuQuintuple()
{
int result=5, i;
for (i=0; i<4;i++)
{
result *= 5;
}
return result;
}

Programme n° 3 : les collections

int main(...)
{
int notes[6], notessup10[6], i, j=0;

// Ajouts des éléments au tableau
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 - Éléments syntaxiques

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

$

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 (équivalent à 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 nom 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-clés 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-clé 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

| 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

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

Dans cette fonction, trois notions ont été introduites : 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

|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 trois types :

1. Message unaire : il s'agit de message sans arguments. Il est articulé de la manière objetreceveur nommethode ;

2. Message binaire : il s'agit de l'utilisation des opérateurs classiques. Il est articulé de la manière objetreceveur nommethode argument. À noter que le nom de la méthode est composé d'au maximum deux caractères parmi les suivants : +, -, *, /, &, =, >, |, <, ~, et @. Une exception existe, car -- n'est pas utilisé pour des raisons syntaxiques ;

3. Message à mot-clés : il correspond à l'appel de méthode classique. Il est articulé de la manière objetreceveur nomméthode:arg1 suitenomméthode:arg2…

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

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

1. 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-clé ;

2. Règle 2 : les messages entre parenthèses sont prioritaires ;

3. Règle 3 : si tous les messages sont du même type et qu’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

array at:1+3 put:4-5.
" + et - sont des messages binaires, at:put: un message à mot-clés 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 :

Hiérarchie 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é, seuls 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 ;

- 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 trois catégories.

Commençons par les messages liés 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 exécute 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 clé ;

- at: clé : permet d'obtenir la valeur de la case référencée par clé, mais provoque une erreur si clé est absente du dictionnaire ;

- at: clé ifAbsent: [« traitements »] : permet d'obtenir la valeur de la case référencée par clé et exécute traitements si clé est absente du dictionnaire ;

- removeKey: clé : retire la clé et la valeur référencée du dictionnaire ;

- keysAndValuesDo: [:clé :valeur| « traitements »] : exécute traitements pour chaque couple clé/valeur ;

- keysDo: [:clé | « traitements »] : exécute traitements pour chaque clé ;

- 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

| 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 tutoriels viendront plus tard pour vous apprendre à faire de la programmation orientée objet. 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 :

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

Ensuite, choisissez « Transcript » dans le menu open :

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 deux lignes pour la lisibilité si un affichage doit être fait.

Maintenant vous avez deux solutions possibles : soit vous faites un « Do it » comme spécifié dans l'image suivante. Ce cas est plutôt à utiliser si un affichage dans le transcript doit ê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 deux méthodes, il faut sélectionner le code à exécuter, comme dans l'image suivante :

Et enfin, voici le résultat affiché :

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étail l'environnement Squeak et la programmation orientée objet dans Squeak.

VII - Remerciements

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