GAZETTE N°27: Un aperçu d'Icon

Un aperçu d'Icon

Par Clinton Jeffery

Adaptation française de Roland Mas et Pierre Tane


Précédent Suivant Table des Matières

4. Un aperçu d'Icon

4.1 Motivations

Beaucoup de langages proposent des caractéristiques adaptées à certains types d'applications, mais peu présentent des structures de contrôles vraiment puissantes ou de nouveaux paradigmes de programmation. Certes, on peut se sentir à l'aise avec un langage déjà connu, mais si on doit écrire des programmes complexes en un temps limité, on a besoin du meilleur langage pour le travail concerné. Icon est un langage de programmation de haut niveau, qui ressemble à beaucoup d'autres langages de programmation tout en offrant un certain nombre de caractéristiques élaborées qui permettent un fort accroissement de productivité. Mais avant d'en arriver à tout ça, nous allons écrire le plus classique des premiers programmes :

procedure main()
   write("Hello, world!")
end

Si vous avez installé Icon pour Linux, sauvez ce texte dans un fichier de nom hello.icn et lancez icont, l'interpréteur Icon dessus :

icont hello -x

icont vérifie la syntaxe de hello.icn, et transforme le code en instructions pour la machine virtuelle Icon, ce qui donne le fichier hello. L'option -x signale à icont qu'il faut aussi exécuter le programme.

Nous allons introduire de nombreux concepts, donc vous ne devez pas vous attendre à tout comprendre à la première lecture - la seule manière d'apprendre un langage est d'écrire des programmes avec ; donc, récupérez et installez Icon, et considérez ceci comme un aperçu.

4.2 Origines

En dépit de son nom, Icon n'est pas un langage de programmation visuel. Sa syntaxe viendrait plutôt de C et de Pascal. Ce qui est important dans Icon n'est pas que sa syntaxe soit facile à apprendre mais que sa sémantique généralise les idées de SNOBOL4 permettant ainsi d'accroître la puissance des notations fréquemment trouvées dans la plupart des langages de programmation. Cela est important à noter dans la mesure où la plupart des langages qui apportent une vraie puissance (APL, Lisp et SmallTalk, par exemple) le font avec une syntaxe si différente que les programmeurs sont forcés d'adopter une nouvelle manière de penser afin de pouvoir les utiliser. Au contraire, Icon ajoute de la puissance camouflée par des notations familières à la plupart des programmeurs.

Icon a été développé pendant plusieurs années à l'Université de l'Arizona par une équipe dirigée par Ralph Griswold. Actuellement, il est disponible sur de nombreuses plateformes et est utilisé par les chercheurs en algorithmique, compilation et linguistique ainsi que par les administrateurs système et ceux qui programment par plaisir. Son implentation et son code source sont dans le domaine public.

4.3 Variables, expressions et types

La syntaxe d'une expression Icon ressemble à celle de la plupart des langages. Par exemple, i+j représente l'addition arithmétique des valeurs stockées dans les variables i et j, f(x) est un appel à f avec l'argument x. Les variables peuvent être globales ou locales à une procédure, et ainsi de suite.

Il n'y a pas besoin de déclarer les variables. De plus, celles-ci peuvent contenir n'importe quel type de valeur. Cependant, Icon est un langage fortement typé : il reconnaît le type de chaque valeur et il ne vous permet pas de mélanger des types incompatibles dans une expression. Les types scalaires de base sont entier (integer), nombre réels (real numbers), chaînes de caractères (strings) et ensemble de caractères (csets). Les entiers et les chaînes peuvent être de n'importe quelle taille, et les chaînes peuvent contenir n'importe quel caractère. Il existe aussi des types structurés : listes, tableaux associatifs, ensembles et enregistrements. Icon s'occupe automatiquement de la gestion mémoire.

Évaluation des expressions orientées but

L'innovation majeure d'Icon est son mécanisme d'évaluation d'expression. Il évite certains problèmes fréquemment rencontrés dans la plupart des langages de programmation usuels, dans lesquels une expression conduit toujours au calcul d'un résultat. Dans de tels langages, si aucun résultat valide n'est possible, une valeur sentinelle telle que -1, NULL ou EOF (end-of-file) est retournée à la place. Le programme doit alors s'enquérir de telles erreurs en usant de la logique booléenne et de tests if-then. Le programmeur doit, lui, se souvenir des valeurs des sentinelles utilisées dans chaque circonstance. C'est lourd. D'autres méthodes, comme, par exemple, les exceptions, sont utilisées dans d'autres langages, mais elles induisent une certaine complexité et d'autres problèmes annexes.

Succès et échec

En Icon, toutes les expressions sont orientées but. Lors de l'évaluation, chaque expression a pour but de produire un résultat pour l'expression qui l'entoure. Si une expression parvient à produire un résultat, l'exécution qui l'entoure s'exécute comme prévu, sinon, on dit qu'elle est en échec : l'expression qui l'entoure ne peut alors être exécutée et est ainsi à son tour en échec. Ce concept puissant englobe la logique booléenne et l'utilisation des valeurs sentinelles et permet de grandes améliorations. Considérons, par exemple, l'expression i > 3 : si la valeur de i est plus grande que 3, l'expression réussit, sinon, elle échoue.

Les structures de contrôle telles que if attendent un succès donc if i > 3 then ... fait bien ce qu'on en escomptait. Puisque la sémantique de l'expression n'est pas surchargée par la propagation de valeurs booléennes (0 ou 1), les opérateurs de comparaison peuvent alors propager une valeur utile (leur opérande droite), autorisant alors des expressions telles que 3 > i > 7, courantes en mathématiques, mais qui ne marchent pas dans la plupart des langages.

Puisque les fonctions qui échouent n'ont pas besoin de renvoyer un code d'erreur en plus de leurs résultats, supprimer le traitement de cas comme la gestion de la fin de fichier est plus simple, comme on le voit dans :


if line := read() then write(process(line))

Sur un EOF, read() provoque un échec, l'expression d'assignation de la clause if échouant par là-même. Lorsque le test échoue, la branche then n'est pas exécutée, donc l'appel à write() n'a pas lieu. Comme les échecs se propagent à travers une expression, l'exemple ci-dessus est équivalent à write(process(read())

Générateurs

Certaines expressions peuvent, évidemment, produire plus d'un résultat. Ces expressions sont appelées générateurs.

Par exemple, chercher une sous-chaîne dans une chaîne, et renvoyer la position où la sous-chaîne apparaît, peut être fait par la fonction find() :

find('or', 'horror')

Dans les langages usuels, une seule des valeurs de retour possibles sera renvoyée, typiquement la première ou la dernière. En Icon, cette expression est capable de retourner toutes les valeurs, en fonction du contexte d'exécution.

Si l'expression qui l'entoure ne nécessite qu'une valeur, comme dans le cas d'une clause if ou d'une assignation, seule la première valeur du générateur est renvoyée. Si un générateur se trouve dans une expression plus complexe, les valeurs de retour sont alors produites en séquence jusqu'à ce que l'expression toute entière retourne une valeur. Dans l'expression, find('or', 'horror') > 3 , la première valeur produite par find() est 2 ce qui fait échouer l'opération>. Icon rappelle alors find() qui produit 4 et l'expression réussit.

L'opérateur le plus évident est l'opérateur d'alternative |. L'expression expr1 | expr2 est un générateur qui produit son côté gauche suivi de son côté droit si l'expression qui l'entoure le nécessite. Ainsi, dans f(1|2), f est d'abord appelée avec 1, puis, dans le cas où l'on n'obtient pas de résultat, le générateur est rappelé avec 2 pour argument de f. Pour le même opérateur, voici un autre exemple :

x = (3 | 5)

qui est équivalent, mais plus compact que l'expression C (x == 3) || (x == 5). Quand plus d'un générateur est présent dans une expression, ils sont traités dans une file LIFO (NdT : Last In, First Out, i.e. Dernier Arrivé, Premier Sorti).

(x | y) = (3 | 5)

est l'équivalent Icon de l'expression C :

(x == 3) || (x == 5) || (y == 3) || (y == 5)

En plus de | , Icon offre un opérateur de génération !, qui crée des éléments de structures de données, et un générateur to qui produit des intervalles d'entiers. Par exemple, !L crée les éléments de la liste L, et 1 to 10 génère les dix premiers entiers positifs.

En plus de ces générateurs qui produisent des résultats, la plupart des générateurs d'Icon prennent la forme de procédures intégrées ou définies par l'utilisateur. Les procédures seront traitées plus bas.

Itération

Icon dispose de la boucle while usuelle dans laquelle l'expression de contrôle est évaluée avant chaque itération. Pour les générateurs, on dispose d'une boucle alternative dans laquelle le corps de la boucle est exécuté une fois par résultat produit par une évaluation de l'expression de contrôle. La boucle alternative fait appel au mot réservé every et peut être utilisée en conjonction avec l'opérateur to pour émuler une boucle for :

every i := 1 to 10 do ...

L'intérêt de every et to n'est pas tant que vous puissiez implémenter une boucle for : le mécanisme des générateurs d'Icon est un peu plus général. La boucle every vous permet de tirer parti de tous les résultats d'un générateur fournissant un mécanisme d'itération simple. Et every n'est pas limité à des séquences de nombres ou au parcours de structures de données spécifiques comme les itérateurs dans d'autres langages. Il fonctionne sur toute expression qui comporte des générateurs.

every f(1 | 1 | 2 | 3 | 5 | 8)

exécute la fonction f avec les premiers éléments de la suite de Fibonacci. L'exemple pourrait être étendu à tout générateur défini par l'utilisateur produisant la séquence entière des nombres de Fibonacci. L'utilisation des générateurs demande un peu d'expérience mais c'est amusant !

4.4 Procédures

Les procédures sont les blocs de programmation de base de la plupart des langages et Icon ne fait pas exception à la règle. Comme en C, un programme Icon est constitué d'un ensemble de procédures et l'exécution se fait à partir d'une procédure appelée main().

Voici un exemple de procédure typique. Elle génère et somme les éléments d'une liste L dont les éléments ont tout intérêt à être des nombres (ou à être convertibles en nombres).

procedure sum(L)
   total := 0
   every total +:= !L
   return total
end

Un utilisateur peut écrire ses propres générateurs en incluant un suspend expr dans une procédure dans laquelle un résultat doit être produit. Quand une procédure est suspendue, elle transmet un résultat à l'appelant mais reste disponible pour continuer à partir de l'endroit où on l'a quittée et générer de nouveaux résultats. Si l'expression qui l'a appelée a besoin de résultats plus nombreux ou différents pour réussir, la procédure sera rappelée.

L'exemple qui suit crée les éléments à partir du paramètre L, mais filtre et élimine les zéros.

procedure nonzero(L)
   every i := !L do
      if i ~= 0 then suspend i
end

L'expression fail fait échouer la procédure ce qui redonne le contrôle à la procédure appelante sans retourner de valeur. Une procédure échoue également implicitement si le contrôle sort du corps de la procédure.

4.5 Traitement de chaînes de caractères

En plus de l'évaluation d'expressions, Icon offre des fonctionnalités attractives pour réduire l'effort nécessaire à l'écriture de programmes complexes. De son ancêtre SNOBOL4, le grand-père de tous les langages de traitement de chaînes de caractères, Icon a hérité de certaines structures de données intégrées parmi les plus souples et les plus lisibles de tous les langages.

Chaînes de caractères

On accède aux parties des chaînes de caractères d'Icon par l'opérateur d'indice. Les indices désignent les positions entre les caractères et les paires d'indices sont utiisées pour désigner une sous-chaîne. Si s est la chaîne 'hello, world' alors les expressions

   
s[7] :=  'linux'
s[14:19] := 'journal'

changent s en 'hello, linux journal', montrant bien les facilités d'insertion et de remplacement offertes. Un myriade d'autres fonctions intégrées travaillent sur les chaînes de caractères : parmi elles, on peut noter les opérateurs de concaténation (s1 || s2) et de taille (*s).

Recherche dans les chaînes de caractères

L'utilitaire d'analyse de chaînes d'Icon est appelée analyse. Un environnement de recherche est mis en place par l'opérateur ? :

s ? expr

Un environnement de recherche est constitué d'une chaîne et d'une position courante dans celle-ci. Les fonctions de filtrage changent cette position et renvoient la sous-chaîne entre l'ancienne et la nouvelle position. Voici un exmple simple :

text ? {
        while move(1) do
          write(move(1))
       }

move est une fonction qui avance la position de son argument : ce code affiche donc tous les caractères de text un à un. Une autre fonction de filtrage est tab qui configure la position de son argument.

Les fonctions d'analyse de chaînes examinent une chaîne et produisent les positions intéressantes dans celle-ci. Nous avons déjà vu la fonction find qui recherche des sous-chaînes. Par défaut, ces fonctions jettentleur dévolu sur la chaîne en cours d'analyse. Voici une procédure qui extrait les mots de l'entrée standard :

procedure getword()
   while line := read() do 
      line ? while tab(upto(wchar)) do {
         word := tab(many(wchar))
         suspend word
      }
end

upto(c) retourne la position suivante d'un caractère dans son ensemble de caractères c et many(c) retourne la position qui suit plusieurs séquences de caractères provenant de c. L'expression tab(upto(wchar)) avance la position jusqu'à un caractère dans wchar, l'ensemble de caractères qui forme des mots. Enfin, tab(many(wchar)) met la position à la fin du mot et renvoie le mot qui a été trouvé.

4.6 Expressions rationnelles

La bibliothèque de programmes Icon (comprise dans la distribution) offre des fonctions de filtrage d'expressions rationnelles. Pour l'utiliser, ajoutez la ligne link regexp au début de votre programme. Voici un exemple de chercher - remplacer :

procedure re_sub(str, re, repl)
   result := ''
   str ? {
      while j := ReFind(re) do {
         result ||:= tab(j) || repl
         tab(ReMatch(re))
      }
      result ||:= tab(0)
   }
   return result
end<

4.7 Structures

Icon dispose de plusieurs types structurés (non scalaires) permettant d'organiser et de stocker un grand nombre de valeurs de types quelconques (parfois mélangés). Un élément de type table est un tableau associatif dans lequel les valeurs stockées sont indicées par des clés qui peuvent être de n'importe quel type. Une liste est un groupe de valeurs que l'on peut accéder aussi bien par des indices entiers que par des opérations de pile ou de file. Un ensemble est un groupe de valeurs non ordonnées, etc...

Tableaux

Un tableau est créé par le biais de la fonction table qui prend un argument :la valeur par défaut, c'est-à-dire la valeur à retourner quand la recherche échoue. Voici un fragment de code qui affiche le nombre de mots tapés sur l'entrée standard (en considérant que la fonction getword renvoie des mots intéressants) :

wordcount := table(0)
   every word := getword() do
      wordcount[word] +:= 1
   every word := key(wordcount) do
      write(word, '\t', wordcount[word])

(La fonction key crée des clés avec lesquelles les valeurs ont été stockées) Puisque la valeur par défaut de la table est 0, quand un nouveau mot est inséré, la valeur par défaut est alors incrémentée et la nouvelle valeur (i.e. 1) est stockée avec le nouveau mot. Les tableaux grossissent automatiquement lors de l'insertion de nouveaux éléments.

Listes

Une liste peut être crée en enumérant ses éléments :

L := ['linux', 2.0, 'unix']

Les listes sont dynamiques, elles grossissent ou rapetissent par l'intermédiaire des appels à des routines de manipulation de listes telles que pop() ou autres. Les éléments d'une liste peuvent être obtenus soit par les fonctions de manipulation de listes, soit par leur indice :

write(L[3])

Il n'y a pas de contrainte quant au type de valeurs stockées dans la liste.

Enregistrements et ensembles

Un enregistrement est comme un struct en C, sauf qu'il n'y a pas de restriction quant au type stocké dans ses champs. Après déclaration d'un enregistrement :

record complex(re, im)

les instances de cet enregistrement sont créées par une procédure constructeur qui a le nom du type de l'enregistrement et, pour ces instances, on a accès au champ par leur nom :

i := complex(0, 0)
j := complex(1, -1)
if a.re = b.re then ...

Un ensemble est un rassemblement de valeurs qui ont la propriété d'être uniques, à savoir qu'un élément ne peut être présent qu'une seule fois dans l'ensemble.

S := set(['rock lobster', 'B', 52])

Les fonctions member, insert et delete font ce que suggère leur nom. Les intersections, unions et différences ensemblistes sont faites par des opérateurs. Un ensemble peut contenir n'importe quelle valeur (y compris lui-même, ce qui n'est pas loin de confiner au paradoxe de Russell !).

4.8 Graphes

Vu qu'il n'y a pas de restrictions sur les types des objets contenus dans les listes, ceux-ci peuvent aussi être d'autres listes. Voici un exemple montrant comment on peut représenter, avec des listes, un graphe ou un arbre :

record node(label, links)
   ...
   barney := node("Barney", list())
   betty := node("Betty", list())
   bambam := node("Bam-Bam", list())
   put(bambam.links, barney,betty

Un exemple

Nous allons maintenant illustrer les concepts précédemment exposés à l'aide d'un petit exemple. Il s'agit d'un programme qui lit un fichier et génère, pour chaque mot, la liste des lignes où il apparaît. Cependant, nous ne voulons pas garder les mots les plus courts comme « le » ou « la » ou « les »   nous ne compterons donc que les mots de plus de 3 caractères.

global wchar
procedure main(args)
   wchar := &ucase ++ &lcase
   (*args = 1) | stop("Need a file!")
   f := open(args[1]) | stop("Couldn't open ", args[1])
   wordlist := table()
   lineno := 0
   while line := read(f) do {
      lineno +:= 1
      every word := getword(line) do
         if *word > 3 then {
            # if word isn't in the table, set entry to empty list
            /wordlist[word] := list()
            put(wordlist[word], lineno)
         }
   }
   L := sort(wordlist)
   every l := !L do {
      writes(l[1], "\t")
      linelist := ""
      # Collect line numbers into a string
      every linelist ||:= (!l[2] || ", ")
      write(linelist[1:-2])
   }
end

procedure getword(s)
   s ? while tab(upto(wchar)) do {
      word := tab(many(wchar))
      suspend word
   }
end

Si on fait tourner ce programme sur le texte suivant :

Sing, Mother, sing.
Can Mother sing?
Mother can sing.
Sing, Mother, sing!

...on obtient ceci :

Mother  1, 2, 3, 4
Sing    1, 4
sing    1, 2, 3, 4

Nous n'avons pas encore vu tous les éléments utilisés dans ce programme, mais il devrait déjà vous donner une idée de l'esprit du langage.

4.9 Co-expressions

Une autre possibilité d'Icon est constituée par les co-expressions, qui sont des expressions encapsulées dans un contexte d'exécution en série, pour lequel les résultats peuvent être utilisés un par un. Les co-expressions sont plus portables et ont une granularité plus fine que les caractéristiques comparables de la plupart des autres langages. Les co-expressions permettent de « capturer » des générateurs et d'utiliser leurs résultats en plusieurs endroits de votre code. Les co-expressions sont créées par :

create expr

et on accède à un résultat de la co-expression par l'opérateur @.

Prenons un exemple  supposons que nous ayons une procédure prime, qui génère une séquence infinie de nombres premiers, et que nous voulions numéroter chacun de ces nombres au fur et à mesure que nous les imprimons, chacun sur une ligne. La fonction seq d'Icon peut générer les numéros d'ordre, mais il n'y a pas de moyen de générer les éléments des deux opérateurs en parallèle  du moins pas de moyen autre que les co-expressions, utilisées de la manière suivante :

numbers := create seq()
primes := create prime()
every write(@numbers, ": ",@primes)

Vous pourrez trouver plus de renseignements sur les co-expressions sur le site drones, et une description complète est donnée dans le Livre du langage Icon, mentionné plus bas.

4.10 Graphisme

Icon propose un ensemble de primitives graphiques de haut niveau, portables d'une plateforme à une autre. Les implémentations les plus stables sont celles sous X-Window et Windows  les portages vers Presentation Manager, Macintosh et Amiga sont en cours, à des stades de développement divers. Les caractéristiques principales de ces primitives graphiques sont :

  • la simplicité et la facilité d'apprentissage 
  • l'intégration des fenêtres dans les fonctions d'entrée/sortie d'Icon 
  • une gestion propre des événements.

Un petit exemple : le programme suivant ouvre une fenêtre et permet à l'utilisateur de taper du texte et de dessiner à main levée (avec le bouton gauche de la souris), jusqu'à ce que la touche Esc soit pressée. Le clic avec le bouton droit de la souris déplace le curseur texte.

Dans l'appel à open, le mode "g" signifie « graphique ». &window est une variable globale spéciale, qui sert de fenêtre par défaut aux fonctions graphiques. &lpress, &ldrag et &rpress sont des constantes spéciales signifiant respectivement un clic avec le bouton gauche, un appui prolongé sur le bouton gauche et un clic sur le bouton droit de la souris. &x et &y sont des variables globales spéciales, contenant la position de souris associée à la plus récente action de l'utilisateur rapportée par Event. "\e" est une chaîne à un seul caractère, contenant le caractère d'échappement.

procedure main()
   &window := open("LJ example","g")
   repeat case e := Event() of {
      &lpress | &ldrag : DrawPoint(&x,&y)
      &rpress : GotoXY(&x,&y)
      "\e"    : break
      default : if type(e)=="tring" then writes(&window, e)
      }
end

Une description complète des fonctions graphiques est disponible sur le worldwide web à l'adresse http://www.cs.arizona.edu/icon/docs/ipd281.html.

4.11 Fonctions POSIX

Un programme Icon, pour utiliser les fonctions POSIX, doit inclure le fichier d'entête posixdef.icn. En cas d'erreur, les fonctions POSIX échouent, et positionnent la variable &errno  le message d'erreur correspondant peut être obtenu par un appel à la fonction sys_errstr.

Les fonctions Unix qui renvoient une structure en C (ou une liste en Perl) renvoient un enregistrement en Icon. Les champs de cet enregistrement ont des noms similaires à leurs contre-parties Unix : stat renvoie un enregistrement dont les champs sont ino, nlink, mode etc.

Une description complète des fonctions POSIX est disponible à l'adresse http://www.drones.com/unicon/. Nous allons examiner quelques exemples.

4.12 Une implémentation de ls

Voyons comment une version simplifiée de la commande Unix ls peut être écrite. On a besoin de lire un répertoire, et d'effectuer un appel à stat sur chacun des noms de fichiers qu'il contient. En Icon, ouvrir un répertoire se fait de la même manière qu'une ouverture de fichier en lecture  toute lecture par read renvoie un nom de fichier.

f := open(dir) | stop(name, ':', sys_errstr(&errno))
names := list()
while name := read(f) do
   push(names, name)
every name := !sort(names) do
   write(format(lstat(name), name, dir))

La fonction lstat renvoie un enregistrement contenant tout ce que lstat(2) renvoie. Une des différences entre la version Unix et la version Icon est que le champ mode est converti en une chaîne lisible par un humain normal - et non un entier sur lequel il faut bricoler avec les bits (en Icon, la manipulation de chaînes est aussi naturelle que les opérations bit-à-bit).

La fonction qui formate l'information est simple  elle teste si le fichier est un lien symbolique, auquel cas elle imprime aussi la valeur du lien.

link printf
procedure format(p, name, dir)
   s := printf("%7s %4s %s %3s %8s %8s %8s %s %s",
               p.ino, p.blocks, p.mode, p.nlink,
               p.uid, p.gid, p.size, ctime(p.mtime)[5:17], name)
   if p.mode[1] == "l" then
      s ||:= " -> " || readlink(dir || "/" || name)

   return s
end

4.13 Polymorphisme et autres choses amusantes

Il n'y a pas que stat qui utilise des valeurs humainement lisibles - chmod accepte un entier considéré comme champ de bits représentant les permissions du fichier, mais il peut aussi prendre une chaîne, de la même manière que la commande du shell :

chmod(f, "a+r")

Quant au premier argument, il peut s'agir soit d'un fichier ouvert, soit d'un chemin d'accès à un fichier. Comme, en Icon, les valeurs sont typées, la fonction sait à quelle sorte de valeur elle a affaire - plus besoin d'utiliser fchmod ni fstat. Ce genre de remarque s'applique également à d'autres fonctions - par exemple, les fonctions Unix getpwnam, getpwuid et getpwent sont toutes regroupées par la fonction Icon getpw, qui choisit l'action adéquate en fonction du type de son argument :

owner := getpw("ickenham")
root := getpw(0)
while u := getpw() do ...

De même, trap et kill acceptent un signal par son numéro ou par son nom  wait renvoie un état lisible  chown accepte un nom d'utilisateur ou un uid  et select prend en argument une liste de fichiers.

4.14 L'utilisation de select

La fonction select attend que des données soient disponibles sur un ensemble de fichiers. En voici un exemple d'utilisation :ce programme attend des données du clavier, ou des événements graphiques sur la fenêtre. Le programme cesse d'attendre après un timeout de 1000 milisecondes :

repeat {
   while *(L := select([&input, &window], 1000)) do
      ... handle timeout
   if &errno ~= 0 then
      stop("Select failed: ",sys_errstr(&errno))

   every f := !L do
      case f of {
         &input  : handle_input()
         &window : handle_evt()
   }
}

Si la fonction select est appelée sans valeur de timeout, elle attendra indéfiniment. Une valeur de 0 effectue un simple test.

4.15 Accès au réseau

Icon fournit une interface simplifiée aux sockets BSD. En lieu et place des quatre appels système nécessaires en Perl pour faire un serveur TCP/IP, un seul est requis en Icon - la primitive open ouvre des connexions comme des fichiers.

Le premier argument passé à open est l'adresse réseau à laquelle se connecter : hôte:port pour les connexions du domaine Internet, ou un nom de fichier pour les sockets du domaine Unix. Le deuxième argument donne le type de connexion.

Voici un exemple de serveur TCP écoutant sur le port 1888 :

procedure main()
   while f := open(":1888", "na") do
      if fork() = 0 then {
         service_request(f)
         exit()
      } else
         close(f)
   stop("Open failed: ", sys_errstr(&errno))
end

L'option "na" indique qu'il s'agit d'un acceptation sur le réseau (network accept). Tout appel à open attend une connexion réseau, et renvoie un fichier pour cette connexion. Pour se connecter à ce serveur, open est utilisé avec l'option "n" (network connect). Voici une fonction qui se connecte à un serveur finger :

procedure finger(name, host)
   static fserv
   initial fserv := getserv("finger") |
      stop("Couldn't get service: ", sys_errstr(&errno))

   f := open(host || ":" || fserv.port, "n") | fail
   write(f, name) | fail
   while line := read(f) do
      write(line)
end

Simple et élégant, n'est-ce pas ? On pourrait même appeler ça de l'art. Par opposition, l'écriture de code parlant socket en Perl n'est pas très différent de l'écriture de ce même code en C, si ce n'est quelques trucs tordus. C'est fini ! Renoncez à l'obscurité, passez à Icon.

UDP

L'utilisation du réseau en mode non connecté (UDP) est similaire  on utilise "nu" comme deuxième argument d'open, pour signifier une connexion UDP. Un datagramme est envoyé soit par write soit par send, et est reçu par receive. Voici un client simplifié pour le service daytime, qui ressemble à rdate(1) :

s := getserv("daytime", "udp")

f := open(host || ":" || s.port, "nu") |
   stop("Open failed: ", sys_errstr(&errno))

writes(f, " ")

if *select([f], 5000) = 0 then
   stop("Connection timed out.")

r := receive(f)
write("Time on ", host, " is ", r.msg)

Comme UDP n'est pas fiable, on se protège des pertes du receive par un select (avec un timeout de 5000 ms), pour éviter que le programme attende indéfiniment si le paquet de réponse est perdu.

4.16 Icon et les autres langages

On a déjà parlé de Perl et de Java dans le Linux Journal, et nous pensons donc qu'il peut être utile de présenter comment Icon peut interagir comme surcouche de ces usines à gaz.

Perl et Icon

Perl et Icon sont tous deux utilisés dans des buts similaires. Ces langages offrent tous deux des structures de données évoluées comme les listes, les tableaux associatifs, etc  tous deux facilitent l'écriture de petits prototypes de programmes sans les alourdir de déclarations lourdes  tous deux enfin ont été prévus pour être user friendly, c'est-à-dire pour faciliter la programmation plutôt que pour être mathématiquement et logiquement exacts.

Cela dit, lorsqu'on en arrive à la conception du langage, Perl et Icon ne se ressemblent pas du tout. Perl est conçu presque sans structure - ou plutôt, comme Larry Wall le fait remarquer, il ressemble plus à un langage naturel qu'à un langage de programmation. Perl a l'air bizarre, mais au-delà de sa syntaxe très souple, sa sémantique est celle d'un langage impératif conventionnel. Par opposition, Icon ressemble plus à un langage impératif classique au niveau de la syntaxe, mais sa sémantique est plus riche.

Les avantages de Perl

La reconnaissance de motifs de Perl, même si elle ne constitue pas un mécanisme aussi général que le parcours de chaînes en Icon, est plus concise pour la reconnaissance des motifs qui peuvent s'exprimer sous la forme d'expressions rationnelles. La syntaxe de Perl semble naturelle pour tous ces dinosaures que sont les administrateurs systèmes, qui utilisent des programmes comme sh, sed et awk. Il y même des gens pour qui Perl constitue une occasion de se placer parmi une élite, celle des gens capables de maîtriser ses particularités idiomatiques.

Quelques-uns des inconvénients de Perl

Mais regardons de plus près ce qui est (à notre avis) indésirable dans Perl. Ces problèmes ne nient pas les caractéristiques intéressantes de Perl, mais montrent juste que ce langage n'est pas une panacée.

Le nommage est ambigu : c'est une mauvaise idée que de pouvoir donner le même nom à des variables scalaires, des vecteurs et des fonctions. Cela semble utile, mais conduit à du code en écriture seule. Nous pensons que c'est principalement pour cette raison que les programmes Perl sont difficiles à maintenir en état de marche. Il y a même en Perl des choses incroyables : $toto et %toto sont deux choses différentes, mais l'expression $toto{titi} fait référence à un élément de %toto !

Le passage de paramètres est extrêmement confus. Le passage de tableaux par leur nom est tout simplement déconcertant. Même après étude approfondie et en faisant preuve d'une absolue patience, nous ne sommes toujours pas sûrs de la manière d'utiliser *toto en Perl. Et pour compléter le tableau, tous les scalaires sont passés par référence. C'est très peu esthétique.

Pourquoi n'y a-t-il pas de paramètres formels ? Il faut se contenter de quelque chose qui ressemble à un appel de fonction, pour déclarer des variables locales et leur assigner @_. La possibilité de laisser tomber les parenthèses lors d'un appel de sous-programme est également malheureuse  c'est un autre exemple de construction « facile à écrire mais difficile à relire ». Quant à la distinction qui est faite entre les fonctions prédéfinies et les routines définies par l'utilisateur, elle est horrible.

Les variables comme @` sont une mauvaise idée. On considère généralement les caractères spéciaux comme étant des ponctuations, pas des noms (en empruntant la terminologie de Wall). Et les mnémoniques dont il faut se souvenir sont une charge supplémentaire pour le programmeur. (Question ultra-rapide, à vous qui écrivez des programmes en Perl : c'est quoi $( ?)

La distinction entre les contextes de tableau et les contextes scalaires conduit aussi à du code confus. Il est probable qu'avec une certaine pratique de Perl on finit par s'y habituer (voire à l'aimer), mais encore une fois c'est déconcertant. Toutes les mises en garde que l'on peut trouver dans la documentation de Perl contre l'ambiguïté de la nature du contexte dans lequel on se trouve en sont une confirmation.

Java et Icon

Java est une sorte d'intermédiaire entre C et C++ d'une part, et les langages de très haut niveau comme Icon et Perl d'autre part. Java et Icon utilisent tous deux un modèle à machine virtuelle (MV). Celle de Java est à la fois de plus bas niveau que celle d'Icon et plus indépendante de la machine physique sur laquelle elle est simulée, mais ce sont des artefacts dus à l'implantation et il serait possible d'implanter Java sur une MV Icon ou Icon sur une MV Java.

Les différences entre Java et Icon sont des différences de philosophie. La philosophie de Java est que tout est objet, que rien n'est intégré au langage, et que les programmeurs doivent se servir de bibliothèques de classes pour toutes les structures et tous les algorithmes non triviaux. L'absence de surcharge des opérateurs en Java fait que sa notation orientée objet ne permet pas de raccourcis d'écriture comme le fait C++. La simplicité de Java est un repos bienvenu quand on sort de C++, mais sa puissance d'expression est si faible, comparée à celle d'Icon (et celles d'autres langages de très haut niveau) qu'il est difficile de soutenir que Java peut supplanter ces langages. Le plus gros de la part de marché de Java provient de celle de C et C++.

Comment améliorer Java

Java contient quelques graves erreurs. Le Projet Sumatra en a énuméré quelques-unes dans le Java Hall of Shame. La plupart des erreurs de Java sont dues à des péchés d'omission, parce que les concepteurs du langage voulaient être élégants et minimalistes. Nous aimerions voir un dialecte issu de Java comprenant des caractéristiques provenant d'Icon comme l'évaluation orientée but, de Perl comme la reconnaissance des motifs, et d'APL comme les opérateurs numériques array-at-a-time  une description d'un tel dialecte se trouve à l'adresse http://segfault.cs.utsa.edu/godiva/.

4.17 Obtenir Icon

Les utilisateurs qui voudraient se plonger plus sérieusement dans ce langage peuvent se procurer un exemplaire de « The Icon Programming Language », par Ralph et Madge Griswold, publié en 1997 par les éditions Peer-to-Peer Communications (ISBN 1-57398-001-3).

Beaucoup de documentation sur Icon est également disponible sur le site de l' Université de l'Arizona. Il existe aussi un groupe de discussion sur Usenet : comp.lang.icon.

La distribution des sources d'Icon est à l'adresse ftp://ftp.cs.arizona.edu/icon/packages/unix/unix.tar.gz. Les fonctions POSIX sont dans le patch suivant, qu'il est nécessaire d'utiliser pour construire la distribution depuis ses sources : ftp://ftp.drones.com/unicon-patches.tar.gz.

Les binaires Icon pour Linux (exécutables au format ELF pour noyau 2.0, libgdbm 2.0.0, libX11 6.0, libdl 1.7.14, libm 5.0.0 et libc 5.2.18), incluant les supports X11 et POSIX, sont disponibles aux adresses suivantes :


Précédent Suivant Table des Matières

Copyright (c) 1998, Clinton Jeffery - Publié dans le n°27 de la Linux Gazette.

Adaptation française de Roland Mas et Pierre Tane