• Aucun résultat trouvé

Extension du calcul sur les amalgames aux types de bases

Éléments d’implémentation et exemples de calculs sur les amalgames

XI.3 Extension du calcul sur les amalgames aux types de bases

Les règles que nous avons données pour la sémantique opérationnelle des amalgames définissaient des règles de réduction pour les amalgames purs c’est-à-dire uniquement l’ensemble définissant le fonctionnement des références libres, liées, l’opérateur de concaténation, de sélection et le système. Pour rendre cet évaluateur utilisable, il est nécessaire d’y ajouter les constantes et les opérations arithmétiques de base ainsi que la conditionnelle. Nous n’étendrons pas d’avantage la sémantique ici, car c’est l’objet de la dernière partie de ce document que d’étudier l’intégration des amalgames et des GBF dans le langage 81/2.

XI.3.1 Les règles de réduction

Nous étendons la grammaire des expressions pour prendre en compte de nouvelles expressions. Le langage ΣI devient le langage ΣI+ égal à : ΣI augmenté de la conditionnelle IfThenElse(eif, ethen, eelse), des opé-rations binaires +, −, ∗, / et des entiersN. Les sous-langages Σ et ΣC s’étendent naturellement aux nouvelles constructions ainsi que les fonctions précédemment définies.

XI.3.1.1 Réduction d’une opération binaire

binopCte : ρ⊢ c binop c−→ c′′ ( (c ∈N) ∧ (cN) c′′= c binop c (2) binop : ρ⊢ u −→nu ρ⊢ v −→mv ρ⊢ u binop v −→ ubinopv 1 ≤ n + m ≤ 2 (3)

Règ. 11 –Réduction d’une opération binaire entre deux constantes.

La règle (2) définit la valeur d’une opération binaire entre deux constantes. La règle (3) spécifie la valeur d’une opération binaire entre deux expressions. Ces deux expressions doivent être réduites pour pouvoir réellement effectuer l’opération. Nous utilisons ici le même mécanisme de macro-expansion que nous avons décrit pour les précédentes règles de la sémantique. En effet, la règle sur les opérateurs binaires doit s’appliquer si au moins une des deux expressions u ou v ne sont pas des constantes. Plutôt que de spécifier un ordre de réduction (de la gauche vers la droite par exemple), nous spécifions uniquement que cette règle peut s’appliquer si une des réductions en prémisse peut s’exécuter.

XI.3.1.2 Réduction de la conditionnelle

Condd : ρ⊢ e −→ e ρ⊢ IfThenElse(e, u, v) −→ IfThenElse(e, u, v) e 6∈ Bool (4) Condt : ρ⊢ e −→ true ρ⊢ IfThenElse(e, u, v) −→ u e∈ Bool (5) Condf : ρ⊢ e −→ false ρ⊢ IfThenElse(e, u, v) −→ v e∈ Bool (6) Règ. 12 –Réduction de la conditionnelle.

Le traitement de la conditionnelle par les règles (4), (5) et (6) est classique : la condition est évaluée jusqu’à ce que la condition soit un élément de Bool. Tant que la condition est une expression, il n’est pas

souhaitable d’évaluer les branches vraies ou fausses : seule une des deux branches doit être effectivement évaluée, celle qui est sélectionnée par la condition. Une fois la condition devenue un élément de Bool, alors la valeur de la conditionnelle est la valeur de la branche correspondante.

Les opérations que nous avons définies précédemment s’étendent naturellement au traitement de la condi-tionnelle. On considère la conditionnelle comme un opérateur ternaire classique. On définit les extensions sur ΣI+ des fonctions définies sur les termes de Σ :

correctB(l, IfThenElse(u, v, w)) = correctB(l, u)∧ correctB(l, v)∧ correctB(l, w) correctF(l, IfThenElse(u, v, w)) = correctF(l, u)∧ correctF(l, v)∧ correctF(l, w) bind(l, IfThenElse(u, v, w)) ≡ IfThenElse(bind(l, u), bind(l, v), bind(l, w))

IdToRef(IfThenElse(u, v, w)) ≡ IfThenElse(IdToRef(u), IdToRef(v), IdToRef(w))

Lift(n, IfThenElse(u, v, w)) ≡ IfThenElse(Lift(n, u), Lift(n, v), Lift(n, w))

C(l, IfThenElse(u, v, w)) = C(l, u)∧ C(l, v) ∧ C(l, w)

XI.3.2 Calcul d’une fonction récursive

Le calcul des fonctions récursives primitives dans le formalisme des amalgames utilise la conditionnelle que nous avons introduit au début de cette section. Un mécanisme de génération récursive d’arbre avec une décoration de l’arbre lors de sa construction permet la définition de fonctions récursives. Une expression qui est « gelée » est reproduite avec un compteur qui est décrémenté à chaque réplication. Quand la valeur du compteur est nulle, alors on fournit une définition qui « dégèle » l’expression et permet la réduction effective de l’arbre généré. Le schéma général de récursion est explicité par l’expression suivante :

{parameter = {x = x1

− 1},

f ct = If(x0≤ 0, C, F (x0, arg0  f ct0)),

value ={arg = parameter1}  ({x = 4}  fct2)}

Un germe (parameter) définit une constante (x) qui est égale à sa valeur dans le scope englobant, moins un. Le definiendum fct définit une expression qui est égale à la constante C si la constante x du scope courant est égale à zéro, sinon qui est égale à arg0

 f ct0. Enfin, nous avons un système value qui définit une évaluation de fct dans un environnement où arg est lié à parameter et qui fournit une valeur à x. Ces définitions correspondent à une équation récursive dans la mesure où dans fct, arg0est une référence libre qui peut se lier à l’expression arg = parameter1 ainsi que x et fct. La génération de la structure récursive cesse après x « dépliages » (ici 4) du germe. Par exemple, l’évaluation de l’expression ci-dessus a pour résultat :

{parameter = {x = x1− 1},

f ct = If(x0≤ 0, C, F (x0, arg0  f ct0)), value = F (4, F (3, F (2, F (1, C))))}

où F (a, b) représente une fonction quelconque de paramètre a et b.

L’utilisation de ce schéma général de récursion permet le calcul de fonctions récursives classiques telles que factorielle, fibonacci... Nous donnons à titre d’exemple les expressions correspondant au calcul de la fonction factorielle :

{parameter = {x = x1+−1}, f ct = If(x0≤ 0, 1, (arg0  f ct0) x0),

value ={arg = parameter1}  {x = V }  fct2} et fibonacci :

{parameter = {x = x1+−1},

f ct = If(x0≤ 1, 1, arg0  f ct0+ arg0  (arg0  f ct0)), value ={arg = parameter1}  {x = V }  fct2}

où V représente la donnée du calcul. La figure 1 représente le détail du calcul de la fonction factorielle où V à la valeur 6, et qui correspond donc au calcul de 6!, obtenu en 28 étapes de réduction. Les définitions de parameter et fct ne changeant pas, on les a par conséquent remplacées par U et V pour des raisons de concision.

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  If (x0 ≤ 0, 1, arg1  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  arg1  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = x1 + −1}  f ct3}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  If (x0 ≤ 0, 1, arg2  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  arg2  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = x1 + −1}  f ct4}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  If (x0 ≤ 0, 1, arg3  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  arg3  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = x1 + −1}  f ct5}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  If (x0 ≤ 0, 1, arg4  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  arg4  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = x1 + −1}  f ct6}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  If (x0 ≤ 0, 1, arg5  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  arg5  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = x1 + −1}  f ct7}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  If (x0 ≤ 0, 1, arg6  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  arg6  f ct0x0}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  {x = x1 + −1}  f ct8}

{U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  {x = 0}  If (x0 ≤ 0, 1, arg7  f ct0x0)} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  {x = 0}  1} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2 {x = 1}  1} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  3 {x = 2}  2} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  4 {x = 3}  6} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  5 {x = 4}  24} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  6 {x = 5}  120} {U , V, value = {arg = {x = x1+ −1}} {x = 6}  720} {U , V, value = {arg = {x = x1+ −1}} 720} {U , V, value = 720}

Fig. 1 –Le calcul complet de 6! en 28 étapes. U et V représentent respectivement les définitions de parameter et de f ct qui sont constantes.

XI.3.3 Exemple de simulation créé par programme : croissance cellulaire d’une

racine

L’exemple de cette section n’a pas été choisi pour sa pertinence biologique, mais pour montrer sur un exemple simple la construction calculée d’un programme. C’est un exemple de système dont l’espace des états doit être calculé conjointement à l’évolution du système.

L’exemple correspond à la croissance et au fonctionnement d’une racine. On suppose qu’une racine est un ensemble de cellules organisées hiérarchiquement en arbre (au sens informatique du terme). L’activité d’une cellule c est modélisée par une variable dont la valeur dépend des cellules filles.

Il y a trois type de cellules : les cellules terminales qui sont « feuilles de la racine », les cellules de branchement qui ont deux cellules filles, et les cellules normales qui ont une seule cellule fille. Seules les cellules terminales sont capables de croissance : à chaque pas de temps, une cellule terminale se transforme en une cellule de branchement ou en une cellule normale.

La simulation consiste à construire à chaque pas de temps l’arbre (informatique) qui représente la racine, et à calculer simultanément la valeur qui caractérise chaque cellule.

Le programme correspondant est écrit entièrement dans le langage des amalgames, sans faire appel à la notion de stream6. On émule donc l’évolution de la racine dans le temps en la construisant comme on construit l’arbre des appels de factorielle :

{

Cell = {sval = Cval1 , hval = Cval3 },

BranchCell = {sval = Cval2 , hval = Cval4 },

EndCell = {sval = 1, hval = Cval3 },

param = {x = x1− 1},

Fct = if x≤ 0 then ec

6. Il est évident que dans le cadre d’une véritable simulation, on utiliserait l’aspect stream de 81/2, ce qui simplifierait la simulation (dans ce cas, il n’y aurait pas besoin de gérer la récursion). Cependant, nous n’abordons l’intégration des amalgames dans 81/2 que dans la partie suivante. Se contenter des amalgames pour coder y compris l’aspect temporel de la simulation, permet de présenter un exemple auto-consistant.

else(if Random() then c #{next = arg  Fct}

elsecb #{nextLeft = arg  Fct, nextRight = arg  Fct})

Thread = {arg = param, ec = EndCell, c = Cell, cb = BranchCell}  ({x = 3}  Fct)

RealThread = {Cval1 = 1 + next  sval,

Cval2 = 1 + nextLeft  sval + nextRight  sval, Cval3 = hval1,

Cval4 = hval1/2,

loop = val}  (Thread # {hval = loop}) }

La traduction en amalgame associe un système incomplet à chaque type de cellule. Ce système incomplet décrit l’activité de la cellule, mais les références vers les filles de la cellules (f, fl, fr) restent libres. On calcule l’arbre de la racine avec la variable Thread. Cet arbre étant obtenu, on fournit les définitions nécessaires pour instancier le calcul de la valeur de chaque cellule (à savoir, on définit Cval1, Cval2, Cval3, Cval4) : c’est l’équation associée à RealThread. x représente le nombre de pas d’évolution désiré. Voici un exemple de résultat (nous ne donnons que l’expression de RealThread) :

RealThread = {sval = 11 hval = 11/2 nextLeft = {sval = 7 hval = 11/4 nextLeft = {sval = 3 hval = 11/8

nextLeft = {sval = 1, hval = 11/8} nextRight = {sval = 1, hval = 11/8} }

nextRight = {sval = 3 hval = 11/8

nextLeft = {sval = 1, hval = 11/8} nextRight = {sval = 1, hval = 11/8} } } nextRight = {sval = 3 hval = 11/2 next = {sval = 2 hval = 11/2

next = {sval = 1, hval = 11/2} }

} loop = 11 }

Cet exemple montre comment on peut construire un espace arbitraire : l’arbre n’est pas un arbre binaire régulier, mais le hasard intervient dans sa construction. Quand la racine est construite, il faut « la faire fonctionner » ce qui correspond dans notre exemple à propager l’attribut sval des cellules terminales à la racine de l’arbre (par exemple, cet attribut peut représenter la valeur énergétique de la sève) et à faire redescendre l’attribut hval. Ce modèle, bien que primaire, permet par exemple de faire dépendre la croissance de la valeur des attributs (synthétisés ou hérités) et correspond donc à un mécaniste plausible de redistribution des ressources dans la plante7.

XI.3.4 Structuration de code et programmation orientée objets

Les notions de système et de concaténation de systèmes permettent la manipulation d’environnements. La composition de systèmes permet de programmer des enregistrements extensibles. De plus, les valeurs symboliques et la capacité à fournir des définitions manquantes vont nous permettre de définir un comporte-ment similaire à ceux que l’on trouve dans les langages orientés objets. Il est possible de définir et composer des fragments de programmes en exhibant un comportement de classe et d’instanciation de classe que l’on

trouve dans ces langages. Nous allons montrer à travers un exemple comment émuler un style programmation proche de ceux des langages à objets.

Un système représente les notions de classe et de constructeur qui sont utilisées pour créer une instance d’une classe. Les arguments nécessaires au constructeur sont les références libres du système. L’instanciation d’une classe correspond à la concaténation du système avec les arguments requis par le constructeur. L’ajout de définitions supplémentaires, par l’utilisation de la concaténation, correspond au mécanisme d’héritage.

Un système complet (sans références libres) correspond à un objet comme c’est le cas dans les langages à objets. Mais pour ce qui concerne 81/2, les valeurs de l’objet sont des tissus : il n’existe pas de notion de message ni de méthodes. Le modèle objet qui correspond le mieux à l’esprit de ce qui est faisable en 81/2est le modèle « embedding based » où toutes les informations qui concernent l’objet se trouvent dans celui-ci. Il est évident que les mécanismes évolués de protection et d’encapsulation qui sont proposés par les langages à objets sont absents dans le modèle que nous proposons.

Pour illustrer ce style de programmation, on se propose de définir en suivant un style proche des langages orientés objets, une modélisation de la trajectoire d’une planète dans un mouvement circulaire uniforme autour d’une étoile elle-même en mouvement rectiligne uniforme. Pour ce faire, on définit une classe Mobile d’objets animés d’un mouvement. La classe Mobile est représentée par un système avec deux références libres :

– initiale qui représente la position initiale de l’objet, – dp qui représente les déplacements élémentaires de l’objet.

À l’aide de ces références libres, qui sont des vecteurs de deux éléments correspondant aux axes Ox et Oy, le système Mobile définit une position :

M obile = {position@0 = initiale; position = $Mobile  position + dp};

L’attribut position de Mobile est un stream qui représente la trajectoire du mobile au cours du temps. Une fois que Mobile est défini, nous pouvons définir une nouvelle classe d’objets : les objets mobiles avec une vitesse uniforme. La classe U−T rajectoire attend une position initiale (requise par la classe Mobile dont il hérite) et un vecteur de vitesse pour pouvoir s’instancier :

U−T rajectoire = Mobile # {dp = vitesse}

Le système U−T rajectoire est un système avec toutes les définitions du système Mobile car c’est un système étendu par les définitions de dp utilisé pour calculer les déplacements élémentaires avec une trajectoire uniforme (on suppose que vitesse est une constante et est égale à la différence de la trajectoire uniforme entre deux éléments consécutifs du stream). L’opération de concaténation rassemble ces deux systèmes et effectue les liaisons entre la référence libre dp qui apparaît dans Mobile et la définition de dp dans le système anonyme complétant les définitions de U−T rajectoire.

On poursuit avec cet exemple en utilisant Mobile pour représenter la trajectoire circulaire d’une planète en révolution autour d’une étoile (le soleil par exemple) qui se déplace suivant une trajectoire uniforme. La classe C−T rajectoire attend un rayon, un centre pouvant être animé d’un mouvement quelconque, et une vitesse angulaire :

C−T rajectoire = Mobile # { initiale = {centre  0, angle + centre  1},

dp = {dx, dy},

ot = $t,

t@0 = 0.0,

t = ot + vitesse_angulaire,

dx@0 = −angle ∗ 2.0 ∗ cos(t) ∗ sin(t),

dx = −angle ∗ 2.0 ∗ cos((t + ot)/2.0) ∗ sin((t − ot)/2.0)+ centre  0− $centre  0,

dy@0 = −angle ∗ 2.0 ∗ sin(t) ∗ cos(t),

dy = −angle ∗ 2.0 ∗ sin((t + ot)/2.0) ∗ cos((t − ot)/2.0)+ centre  1− $center  1}

Il ne nous reste plus qu’à instancier les classes pour décrire le « système solaire » : Etoile = U−T rajectoire # {vitesse = {1.0, 1.0}, initiale = {0.0, 0.0}}; P lanete = C−T rajectoire # {angle = 1.0, centre = Etoile  position}

La figure 2 illustre la trajectoire résultante de la planète, une trajectoire circulaire se déplaçant autour d’un centre, l’étoile, qui elle, est animée d’une translation uniforme.

0 5 10 15 20 25 30 35 40 45 50 0 10 20 30 40 50 Solar System

Fig. 2 –Simulation d’un mouvement circulaire uniforme autour d’un centre lui-même en mouvement rec-tiligne uniforme.