• Aucun résultat trouvé

3.2 Sémantiques de BSML

3.2.1 Syntaxe d’un mini-langage parallèle applicatif

3.2.3 Sémantique à «petits pas» . . . 26 3.2.4 Sémantique distribuée . . . 34 3.2.5 Imbrication de vecteurs parallèles . . . 38

3.3 Comparaison avec les anciens calculs . . . 39 3.A Annexe : preuves des lemmes . . . 40

3.A.1 Déterminisme de ⊲ . . . 40 3.A.2 Confluence forte de⇀ . . . 40 3.A.3 Équivalence entre ⊲ et⇀ . . . 41 3.A.4 Confluence forte de . . . 44 3.A.5 Équivalence entre et⇀ . . . 45

D

ANSce chapitre, nous allons présenter des sémantiques dynamiques d’un noyau de notre langage, afin d’en faire apparaître des propriétés. Les définitions présentées ici seront largement utilisées par la suite dans les différentes sémantiques des extensions de BSML ou de nos nouveaux langages. Nous supposons que le lecteur est familier avec les notions de sémantique des langages fonctionnels. Si ce n’est pas le cas, il en trouvera une présentation dans [274].

3.1 Introduction au λ-calcul

Leλ-calcul a été développé en 1930 par A. Church pour fonder un formalisme de représentation des

fonc-tions. Il espérait également pouvoir lui adjoindre un formalisme pour un fondement logique des mathé-matiques. Ce calcul s’est montré suffisant pour y exprimer toutes les fonctions calculables (les fonctions récursives) mais Kleene et Rosser ont démontré en 1935 l’inconsistance logique duλ-calcul.

3.1.1 Définition duλ-calcul

Initialement, nous nous donnons un ensemble infini dénombrableX dont les éléments sont appelés les

variables. Les variables sont des termes duλ-calcul. Si t1 ett2sont deux termes duλ-calcul alors (t1t2)

est un terme duλ-calcul qui représente l’application de t1àt2. Enfin six ∈ X et t est un terme du λ-calcul

alorsλx.t est un terme du λ-calcul représentant la fonction qui à x associe t. Définition 1 (λ-calcul).

La syntaxe des termes duλ-calcul est le plus petit ensemble défini par la grammaire suivante : t ::= x | λx.t | (t t) avecx ∈ X

Exprimer formellement la notion de substitution d’un paramètre d’une fonction par son argument effectif n’est pas une chose aisée. La difficulté provient de la notion de variables : libres, liées ou muettes. Intuitive-ment, nous voulons que les expressionsλx.(x x) et λy.(y y) soient considérées comme égales. Elles le sont

à renommage près des variables : ces expressions sontα-convertibles. Cette égalité pose alors le problème

que les termesλy.(x y) et λz.(x z), où x est une variable libre, sont α-convertibles, tandis que ces mêmes

termes, où nous avons substituéx par la variable z, ne le sont plus : nous obtenons λy.(z y) et λz.(z z)

où les variablesy et z sont toujours liées. Dans le deuxième cas, la variable muette z a été capturée par la

fonction. Réciproquement, si nous substituonsx par t dans λx.x et λy.x, nous obtenons des termes non

équivalents :λx.x et λy.t.

La substitution doit donc prendre en compte le problème de la capture des variables libres (non liées) et donc ne substituer que les variables qui ne sont pas liées par unλ dans le terme où nous effectuons la

substitution. Nous définissons formellement l’ensemble des variables libre d’un terme duλ-calcul par : Définition 2 (Variables libres).

L’ensemble des variables libresFV(t) d’un λ-terme t est récursivement défini par FV(x) = x

FV(λx.t) = FV(t) \ {x} FV((t1t2)) = FV(t1) ∪ F V(t2)

Nous pouvons alors formellement définir la substitution :

Définition 3 (Opération de substitution).

L’opération de substitution d’une variablex par un λ-terme t dans un terme t, notéet[x ← t] est

récur-sivement définie par :

y[x ← t] =  t siy = x y siy 6= x (λx.t′′)[x ← t] = (λx.t′′) (λy.t′′)[x ← t] = (λz.t′′[y ← z][x ← t]) si x 6= y et z 6∈ F V(t) ∪ (λy.t′′) (t1t2)[x ← t] = (t1[x ← t] t2[x ← t])

Pour une variable, la substitution consiste simplement à remplacer littéralement la variable par le terme à substituer. Pour une fonction, deux cas se présentent : si la variable est liée par l’abstraction, la substitution n’est pas effectuée pour éviter les problèmes de capture des variables liées, sinon nous commençons par remplacer danst′′la variable liée par une nouvelle variable (dite fraîche)z qui n’apparaît ni dans t ni dans t′′, puis nous remplaçonsx par t. Nous évitons ainsi les problèmes de capture d’une variable libre. Pour une

application, la substitution s’effectue récursivement dans les sous-termes.

La notion de réduction dans leλ-calcul est réduite à sa plus simple expression : calculer la valeur d’une

fonction appliquée à un argument revient à substituer la variable qu’elle lie par son argument dans son corps. Cette réduction, notée→, est couramment appelée β-réduction. Nous notons→ la fermeture réflexive et

transitive de→. Nous pouvons appliquer cette règle dans tous les sous-termes du terme que nous cherchons

à réduire.

Définition 4 (Réduction forte).

La réduction forte duλ-calcul est définie par : (λx.t1t2) → t1[x ← t2]

t → t

Γ(t) → Γ(t) avecΓ ::= [] | λ.x.Γ | (t Γ) | (Γ t) ∀x ∈ X

où[] est un trou qui peut être «rempli» par n’importe quel terme. Ceci nous permet de différencier la règle

de tête et la règle de contexteΓ. Avec ces contextes, nous pouvons réduire en profondeur dans les termes et

ainsi réduire leurs sous-termes.

Il suffit de modifier la grammaire des contextesΓ pour définir une nouvelle forme de réduction. Par

exemple, nous pouvons définir la réduction faible duλ-calcul avec les contextes suivants : Γ ::= [] | (t Γ) | (Γ t)

19 3.1. INTRODUCTION AUλ-CALCUL

Dans ce cas, la règle de contexte ne permet pas de réduire (effectuer un calcul) sous une abstraction : elle est considérée comme complètement évaluée. C’est donc une valeur.

3.1.2 Propriétés

Les définitions et les propriétés que nous rappelons ici ne sont pas spécifiques auλ-calcul. Nous les donnons

dans un cadre général et indiquerons si leλ-calcul les vérifie. Dans cette section, un calcul sera la donnée

d’un ensemble de termesT et d’une notion de réduction R (une relation binaire sur les termes). Définition 5 (Formes normales (valeurs)).

Un terme est en forme normale pour une relationR s’il n’a pas de réduit pour R. Nous noterons N FR

l’ensemble des termes en forme normale pourR (les valeurs) :

N FR

def{t ∈ T | ∀t. ¬(t R t)}

Définition 6 (Confluence).

Nous disons qu’une relationR est confluente si elle vérifie la propriété suivante : ∀x, y, z tel que xRy et xRz ⇒ ∃u yRu et zRu

schématiquement : x y z y

Théorème 1 (Confluence du λ-calcul)

Leλ-calcul est confluent pour R =→

Preuve . voir [21]

Définition 7 (Confluence forte).

Une relation est fortement confluente si elle vérifie la propriété suivante :

∀x, y, z tel que xRy et xRz ⇒ ∃u yRu et zRu

schématiquement :

x

y z

u

Lemme 1

Toute relation fortement confluente est confluente.

λx.((λy.(y x)) x) ≡ λ.((λ.(1 2)) 1) ?  6 +1 ?  6 +1 6  ?+1  ?+1

Figure 3.1 —Codage d’unλ-terme avec les indices de De Bruijn

3.1.3 Substitutions explicites

Leλ-calcul ne définit qu’une seule forme de transformation, la β-réduction. Celle-ci remplace l’application

d’une fonction sur un argument par la valeur en résultant. Cette transformation est basée sur la substitution de l’argument aux variables le référençant. Elle n’est d’ailleurs pas définie dans leλ-calcul mais plutôt

représentée comme une méta-opération du langage dont la complexité n’est pas constante : la complexité dépend du terme auquel la substitution est appliquée. En conséquence, sa transposition dans le code objet d’un programme (code machine) ne se résume pas à une simple séquence d’instructions à effectuer, mais à tout un mécanisme (difficile à prouver) que le code objet doit implanter.

Pour cette raison, et bien qu’il décrive le code source des programmes fonctionnels, leλ-calcul n’est

pas adapté parce que trop imprécis. Lesλ-calculs avec substitutions explicites vont fournir une réponse

formelle à ce problème. Dans ces calculs, la substitution n’est plus une méta-opération mais une réduction (voire plusieurs en pratique) au même titre que laβ-réduction. Ceci a pour conséquence que la

substitu-tion devient une opérasubstitu-tion atomique. Outre une atomicité des opérasubstitu-tions, lesλ-calculs avec substitutions

explicites amènent à se poser des questions sur la nature des composantes du calcul. En particulier, ils nous obligent à préciser ce qu’est une variable (son instanciation), une substitution et une réduction (parallèle ou non).

Les noms de variables posent problème dès qu’il s’agit d’implanter leλ-calcul : à chaque β-réduction, le

système doit gérer le renommage des variables. Alors que pour un être humain, le renommage des variables n’est qu’un détail (uneα-conversion), une machine passerait une trop grande partie de son temps de calcul

à changer ces noms. C’est pourquoi, durant le développement de l’assistant de preuves AUTOMATH, De Bruijn [88] a été amené à décrire unλ-calcul où les variables sont remplacées par des entiers et où le

renommage est une incrémentation (ou une décrémentation) de ces entiers.

Le principe des indices de De Bruijn est de remplacer une variable par le nombre deλ qui la sépare du λ qui la lie. Il n’est alors plus besoin d’étiqueter le λ par une variable, puisque cette information est déjà

contenue dans chaque variable. Par exemple leλ-terme λx.((λy.(y x)) x) devient λ.((λ.(1 2)) 1) (voir à

la figure 3.1). Notons que les entiers (en tant que variables) sont notés avec une barre afin de ne pas être confondus avec des entiers d’un langage de programmation. De Bruijn présente alors unλ-calcul appelé λDB-calcul, confluent et isomorphe auλ-calcul [200]. Malgré tout, la substitution y reste une opération

extérieure (et insécable) au calcul, et cela quelle que soit la complexité des termes manipulés.

Pour remédier à ce défaut, de nombreux auteurs ont proposé desλ-calculs où les substitutions font partie

intégrante des termes. Ces calculs fournissent une atomicité des opérations de substitution à base d’envi-ronnements qui sont adjoints aux indices de De Bruijn. Les présenter tous serait fastidieux1, et nous allons simplement définir leλσw-calcul [216] muni d’une stratégie faible d’appel par valeur.

Définition 8 (λσw-calcul).

Leλσw-calcul est défini par la grammaire suivante :

e ::= n | λ.e | (e e) | e[s] s ::= • | v ◦ s

v ::= λ.e[s]

Les valeursλ.e[s] correspondent exactement à la fameuse notion de fermeture (closure dans la littérature

anglo-saxonne) des langages fonctionnels, à savoir un programmee dans un environnement (substitution) s. Cette substitution lie les variables de e avec leurs valeurs. Les règles de réduction du λσw-calcul sont les

21 3.1. INTRODUCTION AUλ-CALCUL suivantes : 1[v ◦ s] → v n + 1[v ◦ s] → n[s] ((λ.e)[s] v) → e[v ◦ s] (e1e2)[s] → (e1[s] e2[s])

La stratégie est munie des contextesΓ ::= [] | (v Γ) | (Γ e) et de la règle de contexte suivante : e → e

Γ[e] → Γ[e]

On a alors le résultat suivant :

Théorème 2 (Déterminisme)

Sie est une expression sans variable libre ni substitutions et si e[•]→ v 1ete[•]→ v 2alorsv1= v2.

Preuve . Chacune des règles est déterministe. Le contexte ne permet la réduction que d’une unique sous-expression.

La relation→ est donc fortement confluente et par le lemme 1, confluente.

Ce théorème peut se lire comme, «étant donné un termee sans variables libres ni substitutions alors son

évaluation dans un environnement (substitution) vide est déterministe, c’est-à-dire qu’elle conduit toujours au même résultat».

3.1.4 Langages fonctionnels et sémantiques opérationnelles

Leλ-calcul présente l’avantage d’avoir une sémantique propre ce qui représente un atout considérable

pour le développement (et la certification) de programmes. En plus de sa simplicité, il offre une grande expressivité calculatoire (il est Turing-complet). Malgré tout, il est inefficace algorithmiquement parlant et inutilisable pour le commun des programmeurs : écrire une fonction «utile» enλ-calcul est un travail

titanesque.

Les langages fonctionnels sont des extensions duλ-calcul permettant une programmation simple et

ef-ficace des fonctions. Ils sont donc des langages réalistes pour la programmation2. Ils conservent une sé-mantique propre et l’introduction de structures de données permet la facilité d’écriture des programmes. La clarté de la sémantique des langages fonctionnels et leurs ressemblances avec les mathématiques ont fait qu’ils se sont imposés comme langages de base pour l’enseignement, la recherche et, à moindre mesure, l’industrie (bien que leur utilisation dans ce domaine s’accroît). Les intérêts de définir la sémantique d’un langage de programmation sont :

1. De spécifier le sens des programmes écrits (ce qu’ils calculent) ;

2. De définir formellement le fonctionnement (le comment) de leurs calculs ;

3. De préciser quels sont les programmes qui sont syntaxiquement corrects mais sémantiquement ab-surdes :1 + (λx.x) ;

4. De permettre des études formelles sur les programmes ; par exemple, une étude des coûts des pro-grammes : quel programme sera alors le plus efficace ?

5. De permettre des optimisations (au niveau des algorithmes ou de la modularité du code).

Dans la littérature, nous trouvons trois méthodes pour spécifier la notion de programme absurde. La pre-mière est la sémantique à grands pas (aussi appelée sémantique naturelle) où les programmes absurdes sont ceux qui ne s’évaluent pas. Mais cette sémantique ne permet pas de différencier les programmes absurdes des programmes qui bouclent (qui ne se terminent pas).

La deuxième consiste à rajouter un terme err au langage et des règles de réduction pour tous les cas absurdes (qui s’évaluent en err). Nous avons alors trois scenarii possibles :

1. Le programme s’évalue vers err, c’est un programme absurde ;

2Dans le cadre de cette thèse, il serait trop long de présenter un langage fonctionnel dans son intégralité (par exemple OCaml). Nous nous réferons à [62, 77] pour une description complète d’un langage fonctionnel comme OCaml : syntaxe, sémantique (in)formelle, bibliothèques, exemples etc.

2. Le programme s’évalue vers un terme qui n’est pas err, c’est un programme valide ; 3. Le programme s’évalue à l’infini : il boucle.

Toutefois, cette approche n’est pas satisfaisante, car elle oblige à rajouter beaucoup de règles au risque d’en oublier. De plus, ces approches ne s’intéressent pas aux étapes de calcul, mais seulement au résultat final, ce qui ne prête pas bien à la description des machines abstraites (voir au chapitre 4).

La dernière possibilité est d’utiliser une sémantique à «petits pas» (sémantique à réduction), qui décrit toutes les étapes de calcul, et de définir une notion de valeur, c’est-à-dire un programme valide en forme normale. Ceci permet aussi d’étudier l’exécution des programmes qui ne terminent pas (par exemple les serveurs). Nous avons alors trois scenarii possibles pour l’exécution d’un termee :

1. e s’évalue vers une valeur en un nombre fini d’étapes : e → e1→ · · · → en → v, c’est-à-dire e→ v ;

2. e se réduit à l’infini : e → e1→ · · · → en→ · · · ;

3. e se réduit en une forme normale qui n’est pas une valeur : e → e1 → · · · → en /→. C’est un

programme absurde.

3.2 Sémantiques de BSML

Comme nous venons de le voir, les sémantiques opérationnelles apportent un formalisme aux calculs. Dans cette section, nous allons décrire trois sémantiques pour un mini-langage BSML, qui auront chacune leurs avantages et inconvénients.

3.2.1 Syntaxe d’un mini-langage parallèle applicatif

Raisonner sur une définition complète et exhaustive d’un langage fonctionnel et parallèle comme BSML serait trop complexe. Pour notre propos et afin de simplifier la présentation, cette section introduit un mini-langage applicatif qui peut être considéré comme un noyau de BSML. Ce mini-mini-langage a la forme d’un calcul mais n’en est pas un, à proprement parler, puisqu’une stratégie d’évaluation sera donnée. Nous ferons donc un abus de langage en l’appelant calcul par opposition à BSML. Par la suite, nos termes seront notés avec éventuellement des indices3.

Définition 9 (Langage source).

Les expressions initiales sont définies par la grammaire suivante :

ep ::= λx.ep abstraction (fonction)

| (epep) application

| (ep, ep) paire

| mkparep création d’un vecteur parallèle

| applyepep application parallèle

| putep communication

| projep projection globale

| c constante

| op opération prédéfinie

| x variable

| µx.ep définition d’une fonction récursive

| if epthenepelseep conditionnelle

Ces expressions sont celles données par le «programmeur». Ce sont donc les termes de notre mini-langage de base. L’ensemble des constantes contient par exemple les entiers, les booléens etc. Nous utili-sons aussi la constante nc (pour «no-communication») qui correspondra auNonede OCaml. Les opérateurs peuvent être les opérations arithmétiques, logiques etc. Cet ensemble contient aussi deux opérations d’iden-tité un peu particulières : GloId et LocId. La première ne peut être exécutée que dans le contexte global (en dehors d’un vecteur parallèle) et a contratrio la deuxième uniquement dans un contexte local (c’est-à-dire dans un vecteur parallèle). Le chapitre 9 sur les entrées/sorties en BSML donnera des exemples

23 3.2. SÉMANTIQUES DE BSML

plus concrets4de ce genre d’opérations. Notons queλx.epse traduit en OCaml parfun x→eetµf.e par

(let rec f = e in f).

Comme pour leλ-calcul (et pour les mêmes raisons), nous utilisons des substitutions explicites dans nos

sémantiques.

Définition 10 (Expressions des sémantiques).

Nos expressions ont donc la forme suivante :

e ::= (e)[s] expression munie d’une substitution

| λ.e abstraction

| (λ.e)[s] fermeture

| (e e) application

| (e, e) paire

| mkpare création d’un vecteur parallèle

| applye e application parallèle

| pute communication

| proje projection globale

| c constante

| op opérateur

| n variables de substitution (indices)

| µ.e définition d’une fonction récursive

| ife then e else e conditionnelle

| he, . . . , ei vecteur parallèle de taillep | [e, . . . , e] tableau fonctionnel où op ::= op ∪ {delpar, init, access, send}.

Notons que nous avons ajouté les vecteurs parallèles de taillep fixe, ainsi que des tableaux (qui seront

purement fonctionnels, c’est-à-dire sans effets de bord). Nous aurons donc une sémantique par valeur dep.

L’ensemble des opérateurs est étendu avec des opérations internes au calcul : la suppression du constructeur de vecteur parallèlehi, la création d’un tableau purement fonctionnel, l’accès à ces valeurs et l’échange de

valeurs entre les processeurs. Notons que nous différencions une abstraction (avec sa substitution)(λ.e)[s]

et une fermeture. Bien qu’au niveau sémantique, il n’y ait pas de différence, la création d’une fermeture a néanmoins un coût. En les différenciant, nous allons pouvoir ajouter une règle de création de la fermeture et lui donner un coût5.

Définition 11 (Substitutions et valeurs).

Les substitutions et les valeurs (sous-ensemble des expressions) sont classiquement définies par :

s ::= • substitution vide

| v ◦ s valeur suivie de la suite de la substitution

v ::= op | c | (λ.e)[s] | (v, v) | hv, . . . , vi

La traduction des «expressions du programmeur» en nos expressions, c’est-à-dire le remplacement des variables par des indices de De Brujin, se définit inductivement (figure 3.2) oùE est un environnement de

substitution des variables (un dictionnaire entre les variables et les indices) défini par :

E ::= • | {x 7→ n, E}

avec la fonction suivante de mise à jour de l’environnement :

Rx(•) = •

Rx({x 7→ n, E}) = Rx(E)

Rx({y 7→ n, E}) = {y 7→ n + 1, Rx(E)} siy 6= x

4Un exemple évident d’opération qui ne peut pas être exécutée globalement est une fonctionrand:int→intqui retourne un en-tier «aléatoire». Si cette opération était exécutée globalement, nous n’aurions plus la même valeur dans le contexte global, ce qui entraînerait des problèmes comme le montre le chapitre 9.

5Dans de futurs travaux, nous voudrions comparer formellement les coûts prédits par une analyse statique avec ceux d’une séman-tique opérationnelle et les temps d’exécutions réelles.

TE(λx.ep) = λ.T{x7→1,Rx(E)}(ep) TE(µx.ep) = µ.T{x7→1,Rx(E)}(ep) TE((ep1ep2)) = (TE(ep1) TE(ep2)) TE((ep1, ep2)) = (TE(ep1), TE(ep2)) TE(mkpar ep) = mkparTE(ep) TE(apply ep1ep2) = applyTE(ep1) TE(ep2) TE(put ep) = putTE(ep) TE(proj ep) = projTE(ep) TE(c) = c TE(op) = op

TE(if ep1thenep2elseep3) = if TE(e1) then TE(e2) else TE(e3)

TE(x) = n si E = {· · · , x 7→ n, · · · , •}

Figure 3.2 —Instanciation des variables en des indices de De Brujin

Cette mise à jour de l’environnement est effectuée lors de la traduction d’une expression de la formeλx.e

ouµx.e. En effet, ce sont les lieurs de notre calcul, et dans ce cas, la variable x sera maintenant l’indice 1 et toutes les autres variables verront leurs indices incrémentés. C’est ce que fait R quand elle modifie

l’environnement de substitution des variables.

Propriété 1

Soit l’expressioneptelle quee = T(ep), alors epest sans variables libres (ainsi quee par conséquent).

Preuve . Par induction triviale sur la traduction de l’expressionep

.

Notons que, considérant notre calcul comme le noyau de notre langage BSML, le fait de ne pas avoir de variables libres n’est pas un souci du point de vue de l’expressivité : elles seront de toutes façons systéma-tiquement rejetées par un compilateur lors de l’analyse statique du terme (ou lors de sa compilation).

Documents relatifs