Le Pi-calcul (ou π-calcul) est un langage de programmation théorique inventé par Robin Milner. Ce langage occupe dans le domaine de l'informatique parallèle et distribuée un rôle similaire à celui du λ-calcul dans l'informatique classique.
En tant que langage de programmation théorique (ou langage formel), le π-calcul ne vise pas à permettre de construire des programmes exécutables. L'objectif du π-calcul est de permettre d'étudier les concepts importants en programmation parallèle et distribuée, afin :
Afin de construire un programme exécutable, il est nécessaire de passer par un langage de programmation concret dérivé (ou inspiré) du π-calcul, tel que JoCaml, Acute ou Nomadic Pict.
En pratique, un programme écrit en π-calcul (ou terme) décrit systématiquement un processus, généralement noté P, Q, R… Les processus représentent aussi bien les processus légers en anglais : threads ou processus lourds et aucune hypothèse n'est faite sur l'emplacement du processus : à ce niveau d'abstraction, celui-ci peut être exécuté sur n'importe quel composant ou n'importe quel ordinateur. Un langage de programmation concret ajoutera un compilateur ou un environnement d'exécution intelligent ou nécessitera plus de précisions pour savoir où placer le processus.
Plutôt que des données explicites, les programmes écrits en π-calcul manipulent des noms, le plus souvent notés a, b, c… x, y, z. Un nom est une construction abstraite qui peut, à ce niveau, représenter n'importe quel type d'information. Les noms sont utilisés comme des canaux : tout processus qui connaît le nom a peut écrire sur a ou lire depuis a. Les canaux diffèrent des variables et des constantes au sens où chaque lecture doit correspondre à une seule et unique écriture -- le processus qui écrit sur a envoie donc une information au processus qui lit sur a. Les informations, de nouveau, sont des noms. Un langage de programmation concret ajoutera des types de base (entiers, booléens) et des structures de données traditionnelles.
En particulier, un système est décrit uniquement sous la forme d'un terme : en π-calcul, il n'y a pas de différence, syntaxique ou sémantique, entre le code et les données.
Note : il existe plusieurs variantes canoniques du π-calcul. Cet article présente le π-calcul polyadique asynchrone à réplication gardée avec comparaison de noms.
Le processus le plus simple est le processus nil, noté 0. Ce terme ne fait rien – il s'agit d'un processus qui a terminé de s'exécuter.
Un terme plus compliqué est l'émission (asynchrone) d'un message : le processus émet sur le canal a l'information b. L'émission est asynchrone au sens où le processus n'attend pas de réponse et continue son exécution même si aucun processus n'a reçu l'information. Comme il est très facile d'écrire une macro pour la communication synchrone à partir de cette primitive de communication asynchrone, il ne s'agit pas d'une limitation.
La réception d'un message se note et se comporte de la manière suivante : si un processus de la forme est présent, il y a communication entre les processus. Le processus récepteur continue alors sous la forme , c'est-à-dire comme le processus P dans lequel le nom x représente le message b. Ceci est similaire à un appel de fonction dans un langage traditionnel. Les différences principales sont les suivantes :
goto avec paramètres ou à un join avec paramètres qu'à un appel de fonction proprement dit.Si P et Q sont deux processus, est la composition parallèle de P et Q, qui représente le fait que P et Q sont exécutés simultanément.
Un processus peut créer un nouveau nom. Comme tous les noms, celui-ci sera a priori un canal de communication. Ainsi, le processus (prononcer new x P) crée un nouveau canal, qui sera désigné sous le nom x dans P. Contrairement à l'opération new x ou dans de nombreux langages, produit une constante x. De plus, comme l'état entier du système est représenté dans la syntaxe du terme (sans mémoire), représente aussi le fait que x a été créé à un moment ou à un autre et que P est susceptible d'utiliser x. Par la suite de l'exécution, si est en parallèle avec le processus Q (ce que nous noterons ) et si P communique la valeur de x à Q, le terme deviendra pour représenter le fait que P et Q peuvent tous deux utiliser x.
Un processus peut tester l'égalité entre deux noms. Ainsi, si a et b représentent le même nom, réagira comme P, sinon comme Q.
La notation P + Q dénote un processus qui se comporte tout aussi bien comme P que comme Q.
Il est possible de faire référence à une infinité de copies d'un processus P par la notation !P. Cette syntaxe n'a donc pas la signification usuelle not P utilisée par la plupart des langages de programmation !
Dans cet exemple, nous construisons progressivement un gestionnaire d'impression (print spooler).
Le rôle du gestionnaire d'impression est de recevoir des requêtes d'impression, de localiser une imprimante disponible et de transmettre la requête à l'imprimante. Si aucune imprimante n'est disponible, la requête est mise en attente. Lorsqu'une imprimante a terminé un travail, elle est considérée comme de nouveau en attente.
Plus formellement, une imprimante est représentée par un canal , … Effectuer une impression sur une imprimante revient à envoyer un message sur ce canal composé
Pour cet exemple, la réserve d'imprimantes peut être représentée comme un ensemble de messages émis sur le canal , chacun contenant le nom d'un canal . Localiser une imprimante disponible revient donc à recevoir un nom d'imprimante sur le canal . Remettre l'imprimante dans la réserve revient à réémettre le nom de l'imprimante sur ce même canal. Pour « protéger » la réserve d'imprimante, le nom du canal sera considéré comme local, c'est-à-dire que le gestionnaire sera préfixé par .
Pour cet exemple, faire appel au gestionnaire d'impression consiste à envoyer un message sur le canal , composé
La réception sur est répliquée car le service peut être invoqué un nombre arbitraire de fois.
Ainsi, le gestionnaire d'impression peut être défini par
Dans ce qui précède
Par suite, invoquer le gestionnaire d'impression consistera à placer en présence du terme précédent un terme tel que
Ce terme émet une requête d'impression et attend que la requête soit achevée pour exécuter .
Note : cette section s'apparente à observer la trace d'un programme à l'aide d'un débogueur et est donc, de manière prévisible, peu lisible.
Mis en présence, le processus , le processus et un ensemble d'imprimantes réagissent de la manière suivante :
À l'aide des divers outils du π-calcul, il est possible de caractériser le gestionnaire d'impression par ses propriétés.
Tout comme les fonctions et variables dans un langage de programmation traditionnel, les canaux du π-calcul peuvent être typés afin de certifier que les messages qui transitent sur ces canaux ont toujours le même format. Dans le cas du gestionnaire d'impression :
Un système de types simple permet d'associer à chaque nom de canal le type des informations qui peuvent circuler sur ce canal. Une manière de noter cela est
Ces informations peuvent aisément être vérifiées statiquement (i.e. au cours de la compilation) par un logiciel approprié.
Tout comme les modules et les classes dans de nombreux langages de programmation introduisent des notions de méthodes et de champs publics ou privés, les canaux du π-calcul peuvent être typés afin de certifier que seuls certains agents peuvent écrire sur un canal ou que seuls certains agents peuvent lire sur un canal. Ainsi, dans le cadre du gestionnaire d'impression, on souhaitera probablement s'assurer que
Typiquement, afin d'assurer le respect de cette politique de sécurité, un système de types examinera de quelle manière les noms de ces canaux sont distribués (étape assimilable à la liaison dynamique dans les langages traditionnels).
Ainsi, les informations de type du gestionnaire d'impression pourront être complétées en . En adoptant ce type, le gestionnaire d'impression s'engage à respecter la politique suivante : est un canal qui permet d'émettre ou de recevoir. Sur le canal transitent deux informations : un Document, qui est un canal en lecture seule, et un canal, en écriture seule.
Note : cette garantie de sécurité, tout comme le typage public/privé/protégé/… ou interface/mise en œuvre procède d'une analyse statique. Un composant inconnu et non vérifié peut donc violer la politique de sécurité. Pour assurer que seuls des composants vérifiés sont exécutés sur une machine, il peut être nécessaire de faire appel à des techniques telles que le Code Porteur de Preuves ou/et des vérifications dynamiques de typage de code reçu de tierces parties.
Les langages de programmation suivants sont des implémentations du π-calcul ou de ses variantes :