Table des matières

Symboles

Les symboles sont les noms que le programmeur va pouvoir établir pour étiqueter les éléments qui constituent les programmes: les fonctions, les propriétés, variables, etc…

Il est toutefois intéressant de retenir que les symboles sont des valeurs en eux-mêmes et que le langage est capable de les manipuler au même titre que tout autre type de valeur.


Résolution des symboles

Par défaut, un symbole est résolu immédiatement.

La résolution d'un symbole consiste à le remplacer par la valeur qu'il représente. Ceci constitue l'implémentation élémentaire des variables dont nous parlerons un peu plus loin.

Cependant, il est intéressant de noter qu'Abstrasy permet de bloquer la résolution d'un symbole très facilement. Il suffit de “quoter” ce symbole en ajoutant une apostrophe juste devant.

Par exemple, si le symbole X est défini comme le nom d'une variable dont la valeur est 10, X sera remplacé par la valeur 10 immédiatement. Par contre, si nous écrivons 'X, la valeur sera le symbole lui-même (donc sans résolution) : 'X. Dans ce cas, le symbole 'X est pris tel quel comme une valeur identité.

Ce mécanisme sera utile pour définir de nouvelles variables.

Par exemple, dans:

(define 'X 10)
(display X)

10

L'opérateur define reçoit le symbole 'X et le nombre 10 comme paramètres. Comme la valeur de 'X, c'est 'X, le paramètre fourni bien le nom symbolique de la variable à définir.

Sans l'apostrophe, X serait immédiatement résolu et une erreur se produirait puisque la variable X n'existe pas encore.

Dans la ligne suivante display reçoit bien la valeur 10 comme paramètre puisque X est résolu et remplacé par la valeur 10.


Règles de nommage

Les symboles peuvent être librement créés et utilisés dans les programmes. Cependant, ils doivent respecter un ensemble de règles de nommage particulières.

Ces règles de nommage sont très importantes car elles permettent non seulement de former des noms de symbole corrects , mais aussi elles documentent les données qu'elles symbolisent. Elles permettent donc non seulement de nommer les éléments que l'on souhaite utiliser dans les programmes, mais aussi de les manipuler d'une manière plus sûre. Cela n’exclut cependant pas une grande liberté de choix de noms symboliques. Voici l'ensemble des règles de nommage recommandées:

  1. Ils commencent toujours par une lettre en majuscule [A-Z] ou en minuscule [a-z], un dollar '$', un point d'exclamation '!' ou un tiret bas '_'.

  2. Si un nom symbolique ne commence pas directement par une lettre, mais par un caractère non alphabétique, ce caractère doit être immédiatement suivi par une lettre.

  3. Le corps du nom symbolique peut contenir toute les lettres majuscules ou minuscules non accentuées [A-Z ; a-z], les chiffres [0-9], le point '.', le tiret '-', et le tiret bas '_'.

  4. En outre, les noms symboliques peuvent se terminer par un point d'interrogation '?' ou un point d'exclamation '!'.

  5. Enfin, lorsqu'un nom symbolique est identique à un mot réservé du langage, on peut lever l'ambiguïté en le préfixant par un caractère arobase '@'.

A titre d'exemple purement indicatifs, les symboles suivants sont correctement formés:


Les symboles d'identité

Les symboles qui commencent par un point d'exclamation '!' ne sont pas assignables. On peut toutefois les utiliser comme des identités. En effet, un symbole conserve toujours la même identité unique dans l'ensemble du code d'un programme.

Pour éviter toute tentative d'évaluation, les symboles d'identité doivent être quotés (précédés par une apostrophe, comme par exemple: '!left ou '!right).


Conventions de nommage facultatives

En plus des règles de nommage qu'il faut absolument respecter, il est conseillé de conformer autant que possible les nouveaux symboles aux conventions de nommage facultatives du langage. Cela fait partie des bonnes pratiques du langage de programmation.

Si vous ne respectez pas ces convention facultatives, cela n'empêchera pas vos programmes de fonctionner correctement. Toutefois, ils pourront être difficiles à relire, à maintenir, ou encore à être réutilisés. Il est donc fortement conseiller de conformer vos symboles aux conventions de nommage facultatives du langage de programmation.


Convention de nommage des prédicats

Il est plus facile de reconnaître une fonction ou une méthode de prédicat si son nom se termine par un point d'interrogation ?.

Cette convention est en adéquation avec celle des mots réservés du langage où les nom des opérateurs prédicats se terminent aussi toujours par un point d'interrogation (par exemple: boolean?, ==?, <?, >?, integer?, etc…).


Convention de nommage des fonctions et méthodes qui produisent un effet de bord

Il est également plus sûr signaler une fonction ou une méthode qui produit un effet de bord. Il peut s'agir par exemple d'une fonction qui affecte la valeur d'un de ses paramètres. Pour cela, il convient de signaler cette particularité en terminant le nom symbolique d'une telle fonction par un point d'exclamation !.

Cette convention est également conforme à celle utilisée dans les mots réservés du langage où les opérateurs à effet de bord sont signalés par un nom qui se termine par un point d'exclamation (par exemple, les opérateurs atomiques comme set!, incr! ou encore fetch-and-add!).

Notez toutefois que l'opérateur swap!! a la particularité de signaler à la fois un effet de bord et aussi un usage déconseillé dans un contexte parallélisé. C'est pourquoi, il se termine par deux points d'exclamation, chose qui n'est pas permise dans les symboles.


Convention de nommage des fonctions et méthodes d'implémentation (partielle)

Lorsqu'un nom de symbole commence par un underscore '_', cela indique que le symbole désigne une fonction ou une méthode d'implémentation.

Généralement, les fonctions d'implémentation sont des fonctions appelées par d'autres fonctions qui s'en servent pour implémenter tout ou partie d'une procédure. Il est donc tout à fait possible qu'une méthode d'implémentation ne fournisse qu'une partie du programme effectivement réalisé par une fonction.

On parle donc assez souvent d'implémentation partielle. C'est notamment le cas des méthodes _impl_incr_mutator, _impl_decr_mutator ou encore _impl_add_mutator qui implémentent partiellement non seulement les opérateurs incr!, decr! et add!, mais aussi fetch-and-incr!, fetch-and-decr! ainsi que fetch-and-add!.


Variables

Les variables sont des symboles qui associent un nom à une valeur.

Le nom d'une variable correspond à un symbole que l'on définit à cet effet. De plus toute variable est associé à une valeur. Cette valeur peut être un nombre, une chaîne de caractères ou une quelconque valeur de tout autre type.

(define 'x 10) # A partir d'ici, la variable x correspond au nombre 10

Lorsqu'une variable est lue, son symbole est résolu en étant substitué par la valeur associée.

(display x)    # Comme si on avait écrit (display 10) : 

10
Ready.


Type statique du contenant

Dans un script Abstrasy, le symbole d'une variable (son identifiant) fournit une documentation sur le contenant. Il s'agit plus précisément d'une information relative à la mutabilité de la variable. On dit aussi que le type du contenant est statique parce qu'il fait partie du symbole.


Les variables immuables

Le symbole d'une variable immuable (parfois appelée “read-only” dans d'autres langages) commence toujours par une lettre [A-Z] ou [a-z].

Lorsqu'on définit une variable immuable, la valeur de cette variable ne peut plus être modifiée dans le contexte courant. La variable peut toutefois être redéfinie dans un autre contexte, même éventuellement imbriqué.

(define 'x 10)
(if (=? x 10) {
  (define 'x 20)
  (display "1. x = " x)
})
(display "2. x = " x)

1. x = 20
2. x = 10

Une variable immuable contient la référence directe de la valeur (qui est une donnée dans la mémoire vive de l'ordinateur).


Les variables mutables

Une variable mutable peut voir sa valeur affectée ultérieurement.

Le symbole d'une variable mutable commence toujours par un caractère dollar $. Cela signifie qu'il est possible de muter la valeur assignée à la variable.

(define '$x 0)
(set! $x (+ $x 1))
(display "$x = " $x)

$x = 1

En réalité, une variable mutable reçoit une référence qui ne peut plus changer ensuite. Cependant, alors qu'une variable immuable reçoit directement la référence de la valeur, une variable mutable reçoit la référence d'une cellule d'indirection. C'est donc cette cellule d'indirection qui stocke la référence de la valeur qui pourra muter.

Ainsi, la référence assignée à la variable est bien immuable, par contre, la référence contenue par la cellule d'indirection peut, quant à elle, être modifiée. Il s'agit donc d'une référence indirecte, mais dont l'usage est totalement transparent pour l'utilisateur.


Opérateur de résolution de porté objet ":"

Un nom symbolique peut aussi être composés de plusieurs symboles. Ceci est particulièrement utile dans le cadre de la programmation orientée objet.

Dans ce cas, un symbole peut décrire le chemin d'accès à une propriété ou une méthode.

Pour cela, on peut utiliser l'opérateur de résolution de portée qui est représenté par le caractère deux points «:».

Ainsi, par exemple, on peut accéder à la propriété hauteur de l'objet rectangle en utilisant le symbole rectangle:hauteur. Ce principe très simple est aussi utilisé d'une manière similaire dans d'autres langages de programmation par objet.

On notera cependant qu'un symbole est toujours considéré d'une manière unitaire. L'opérateur de résolution de portée fait partie du symbole. Ainsi, dans rectangle:hauteur, il n'y a qu'un seul symbole et non deux.

(object 'rectangle {
  (define ':largeur 3)
  (define ':hauteur 4)
  (function ':surface {return (* :largeur :hauteur)}) 
})
 
(display "Largeur du rectangle : " rectangle:largeur)
(display "Hauteur du rectangle : " rectangle:hauteur)
(display "Surface du rectangle : " (rectangle:surface))

Largeur du rectangle : 3
Hauteur du rectangle : 4
Surface du rectangle : 12

Vous aurez aussi probablement noté que la référence de l'objet courant est implicite et peut être omis. Ainsi, lors de la définition des variables ainsi que de la méthode, le symbole commence directement par le signe deux points “:”.


Les symboles spéciaux

Nous avons vu dans la rubrique précédente qu'il est possible de composer des symboles pour pouvoir accéder aux propriétés et aux méthodes des objets à l'aide de chemins d'accès absolus et relatifs. Parlons à présent des symboles spéciaux très utiles dans la composition des chemins relatifs.


Le symbole spécial "."

Pour résoudre les symboles et accéder aux données, Abstrasy utilise une méthode analogue à celle de la résolution des chemins d'accès des fichiers sur un disque. On pourrait voir, tout simplement, la résolution du symbole rectangle:hauteur comme étant le chemin d'accès du fichier «hauteur» dans le dossier «rectangle».

Comme dans un système de fichiers, il est possible de résoudre un symbole relatif à l'objet courant comme s'il s'agissait d'accéder à un fichier à partir du dossier courant. Ainsi, à partir d'une méthode appartenant à un objet, on peut accéder aux propriétés de l'objet d'une manière relative à l'aide du symbole spécial point «.». Ainsi, par exemple, le symbole .:hauteur permet d'accéder à la propriété hauteur de l'objet courant. Le symbole spécial . fournit le contexte de l'objet courant. L'utilisation du symbole spécial . permet toutefois de pouvoir composer des symboles relatifs plus aisément.

(object 'objet {
  (define ':myself .) # La propriété :myself contient l'espace de noms de l'objet.
})

Pour rendre plus concise la composition de chemin, le symbole . peut être omis s'il constitue la premier partie d'un symbole composé. De cette manière, .:hauteur est sémantiquement identique à :hauteur. On peut donc utiliser la forme concise sans que cela n'altère la signification du symbole.


Le symbole spécial ".."

Un des avantages de la programmation par objet consiste à permettre la redéfinition des propriétés et des méthodes d'un objet parent dans un objet dérivé. L'objet dérivé possède donc toutes les caractéristiques de son parent, mis à part ses propres spécifications. On appelle cela l'héritage.

Bien sûr, lorsqu'on redéfinit une méthode, la nouvelle méthode de l'objet dérivé masque simplement la méthode de l'objet parent. La méthode de l'objet parent reste donc toujours accessible. Ainsi, pour accéder au contexte de l'objet parent, il suffit d'utiliser le symbole spécial deux points successifs «..». Donc, si à partir du contexte courant d'un objet, on résout le symbole ..:hauteur, on accède à la propriété hauteur de l'objet parent dont l'objet courant est dérivé. Par analogie, on peut dire qu'il est possible de remonter l'enchaînement de l'hérédité d'un objet comme si remontait dans l’arborescence des dossiers d'un système de fichiers en partant du dossier courant.