Précédent
Suivant

Génèse informatique

2 ième partie

1) Algèbre fondamentale

La recherche du langage de programmation idéal nous amène au premier abord dans une structure fondamentale qu'est l'algèbre libre. Au départ, il n'y a pas de distinction entre programme et donnée, ni de granulation distinguant donnée et liste de données. On part d'une unique donnée notée `alpha`, qui est donc un programme pouvant s'appliquer sur une donnée pour produire une donnée. Et chaque donnée produite est donc également un programme pouvant s'appliquer à son tour sur une donnée pour produire une donnée, etc....

Le langage doit pouvoir noter ces différentes programmations sans les exécuter. Cela se fait par emboitement, en accolant le terme jouant le rôle de programme au terme jouant le rôle d'argument mis entre parenthèse. Ce nouveau terme ainsi assemblé joue le rôle de la donnée produite mais non encore évaluée.

Sans se préoccupé de l'évaluation, on construit ainsi une première structure fondamentale qu'est l'algèbre libre engendrée par un élément-opérateur-unaire `alpha`. Cela se traduit par les deux axiomes suivants :

1. `alpha` est un terme.

2. Tout terme `x` peut s'appliquer à tout terme `y` pour former par emboitement le terme `x(y)`

Exemples de terme : `alpha,  alpha(alpha), alpha(alpha(alpha)), alpha(alpha)(alpha), alpha(alpha)(alpha(alpha))`. Il convient de bien distinguer les parenthèses d'appel accolées à un terme jouant le rôle de fonction, des prenthèses de priorisation. Ici, il n'y a que des parenthèses d'appel. On note `ccL` l'ensemble des termes. Les termes de cette structure `ccL` sont des fonctions unaires de `ccL"→"ccL`.

Classiquement on présente cette algèbre libre à l'aide d'un élément générateur `alpha` et d'une opération binaire d'application notée par absence de symbole en juxtaposant la fonction et son argument d'entrée, et posée associative à droite :  `AA (x,y,z),`

`xyz = x(yz)`

L'associativité à droite n'est posée ici que dans le seul but de donnée une signification à une composition écrite sans parenthèse. Ainsi apparait deux notations, l'écriture classique et l'écriture dynamique :

Notation
classique
 
Notation
dynamique
`xy`
`=`
`x(y)`
`xyz`
`=`
`x(y(z))`
`(xy)z`
`=`
`x(y)(z)`

L'opération binaire d'application n'ayant pas de symbole, on lui donne un second nom `sf"app"`, un alias, pour pouvoir facilement la désigner :

`sf"app"(x,y)= xy`

La structure `(ccL, sf"app"(".,.")"`) est un magma engendré par `"<"alpha, sf"app"(".,.")">"`

2) Fonctions d'arité diverses

Comment pouvons-nous définir une fonction binaire de `ccL"×"ccL -> ccL` ? On peut le faire par simple convention. Exemple de définition d'une fonction `g` binaire à partir d'un terme `h` :

`AAx,AAy, g(x,y) = h(x)(y)`

Lorsque `h` décrit l'ensemble des fonctions de `ccL"→"(ccL"→"ccL)` on démontre que `g` décrit l'ensemble des fonctions de `ccL"×"ccL -> ccL`. En effet, `h` est simplement la currification de `g`. L'argument reste valable pour pouvoir définir des fonctions d'aritée `n` de `ccL^n"→"ccL` et également pour définir des fonctions d'arité variable de `ccL"*""→"ccL`. Exemple de définition d'une fonction `g` d'arité variable à partir de deux termes `h_0` et `h` :

`AA(x_1,x_2,x_3...), `
       `g()=h_0`
       `g(x_1)=h(x_1)`
       `g(x_1,x_2)=h(x_1)(x_2)`
       `g(x_1,x_2,x_3)=h(x_1)(x_2)(x_3)`
        ...

3) Liste

Comment devons-nous étendre notre langage pour introduire ces fonctions d'arités multiples de façon explicite ? Une approche simple existe pour désigner comme un terme, une liste de termes de taille supérieure ou égale à 2. Elle consiste à définir une opération binaire qui jouera le rôle d'opération de concaténation. On note cette opération binaire par le symbole de produit `×` avec une syntaxe centrée. Celle-ci devra donc posséder les propriétés intrinsèques à l'opération de concaténation. Quelles-sont ces propriétés ?

  1. La concaténation de listes applaties, qui correspond au produit de deux termes, comprend un élément neutre qu'est la liste vide notée classiquement par `1`.
  2. La concaténation est associative.
  3. La concaténation est simplifiable.
  4. Et elle possède une propriété de fondation, à savoir qu'il doit exister des termes qui ne sont pas des listes d'au moins deux termes c'est à dire qui ne se décomposent pas en un produit de deux termes autres que `1`, et que tout terme autre que `1` est un produit fini de tels termes.

En effet une telle opération permet de définir de façon exacte une notion de liste applatie de termes, avec la liste vide comme élément neutre. Chaque terme possède une arité de sortie qu'est la taille de la liste qu'il constitue. Le terme `1` à pour taille zéro. La taille de `x"×"y` est égale à la somme des tailles de `x` et de `y`.

Si on ne tient pas compte de ce dernier axiome, dit axiome de fondation, on définit une structure de listes aplaties plus générale, dans laquelle nous pouvons obtenir des quotientages non-triviaux par égalité entre deux termes quelconques.

On ajoute donc une opération binaire `"×"`, et on ajoute les axiomes suivants : `AA(x,y,z),`

Element neutre à gauche :  `(1×x)=x`

Element neutre à droite :  `(x×1)=x`

Associativité :  `(x"×"y)"×"z = x"×"(y"×"z)`

Simplifiable à gauche :  `(z"×"x = z"×"y) => x"="y`

Simplifiable à droite :  `(x"×"z = y"×"z) => x "="y`

Puis il est toujours possible d'engendrer cette opération à partir de l'unique terme `alpha` pour les même raisons de currification décrite précédement :  `x"×"y = alpha(x)(y)`

 
Notation
classique
Notation
dynamique
Axiome de liste aplatie
non fondée :    
`(1×x)=x`  
`((),x) = x`  
`(x×1)=x`  
`(x,()) = x`  
  `x"×"y"×"z = (x"×"y)"×"z = x"×"(y"×"z)`  
  `(x,y,z) = ((x,y),z)=(x,(y,z))`  
 `(z"×"x = z"×"y) => x"="y`   
  `((z,x)"="(z,y))=>x"="y`  
  `(x"×"z = y"×"z) => x "="y`  
  `((x,z)"="(y,z))=>x"="y`  
Génèse :
  `x"×"y =sf"app"(sf"app"(alpha,x),y)`  
  `(x,y) = alpha(x)(y)`   

On choisie une syntaxe où l'appel de fonction est prioritaire sur le produit, faisant que `x"×"yz=x"×"(yz)`. La liste des termes `x,y,z,t` est ainsi désignée par le produit des termes `x"×"y"×"z"×"t`. Toute fonction peut s'appliquer à tout terme et donc peut s'appliquer à toute liste aplatie de termes puisque la liste de termes est un terme. Pour tout terme `x`, la liste singleton `(x)` est identique au terme qu'elle contient `x`. Et la liste vide `( )` désigne un nouveau terme noté `1` qui est l'élément neutre de l'opération de produit `×` :

Notation
classique
 
Notation
dynamique
`1`
`=`
`()`
`x1`
`=`
`x()`
`xy`
`=`
`x(y)`
`x"×"y"×"z`
`=`
`(x,y,z)`
`x"×"y"×"zt`
`=`
`(x,y,z(t))`
`x(y"×"z)`
`=`
`x(y,z)`
`(x"×"y)z`
`=`
`(x,y)(z)`
`(x"×"y)(z"×"t)`
`=`
`(x,y)(z,t)`

Cet élément neutre `1` peut être identifié à `alpha` afin que la strutcture soit toujours monogène.

4) Liste de fonctions

Notre vision fractal d'un programme composé de sous-programmes qui sont des programmes, va nous proposer les voies les plus générales de conception d'un langage de programmation.

La liste étant un terme, se pose alors la question, à quel programme correspond une liste de programmes ? On perçoit deux interprétations possibles, l'une mettant en oeuvre la composition parallèle, l'autre mettant en oeuvre la composition série. Cela définira soit deux sortes de listes ou bien deux sortes d'application.

Composition parallèle :

Les programmes sont appelés des fonctions. Une liste de fonctions telle que `(a,b)` est un terme, et c'est donc aussi une fonction. On choisie d'adopter un langage de programmation mettant en oeuvre l'appel parallèle, c'est à dire `AAx,` :

Notation classique
               
Notation dynamique
`(a,b)(x) = (a(x),b(x))`
`(a"×"b)x = ax"×"bx`

Autrement-dit l'opérateur `sf"app"` est distributif à gauche sur l'opérateur de produit `"×"`. Il découle de cette distributivité que le terme `1` est une fonction qui retourne toujours `1`. En effet : `AA(x,y),`

`x"×"1=x`

`(x"×"1)y=xy`

or la distributivité à gauche de `sf"app"` sur `"×"` aboutie à :

`(x"×"1)y=xy"×"1y`

donc :

`xy = xy"×"1y`

Et comme le produit est simplifiable, il est possible d'enlever le premier élément de la liste, on a donc :

`1=1y`

En conclusion dans ce premier cas le terme `1` est une fonction qui retourne toujours `1`.

Composition série :

Il existe un second choix de langage de programmation qu'est l'appel série, où la liste s'apparente alors de plus en plus à un flux.

L'appel série tel que `(a,b)(x,y,z,t)` va soummettre les données `(x,y,z,t)` à `a` qui n'en prendra que les premières correspondantes à un modèle compatible pour `a`, et le reste sera soumis à `b`. Puis les résultats seront également concaténés.

Pour mettre en ouvre ce mécanisme, il faut que chaque programme (chaque fonction) est un mécanisme de préamption de données, qui ne prendra que les premières données satisfaisant un prés-calcul. Le mécanisme de préemption le plus simple coorrespond à une arité fixe. Sinon la fonction est d'arité variables qui dépend de la liste des données qui lui est soumise. Quel conséquence ce principe a-t-il sur la liste vide ? Considérons des termes de taille un, `x,y,z,t`, et considérons des fonctions d'arité fixe d'entrée un, `a,b`. Nous avons :

`(a,b)(x,y,z,t) = (a(x),b(y))`

`(a,(),b)(x,y,z,t) = (a(x),()(),b(y))`

Donc la liste vide notée `()` est une fonciton d'arité d'entrée nulle et retourne la liste vide : `()()"="()`, donc d'arité de sortie également nulle.

est-il pertinent de distinguer ces deux sortes de compositions, série ou parallèle, en créant deux sorte de listes `(...)` et `[...]` pour spécifier les fonctions ou les données ?, où en créant deux sortes d'applications `sf"app"` et `sf"app"_∥` ? Rien pour l'instant ne permet de choisir.

5) Flux de données

Le flux de donnée n'a de sens que si les fonctions metttent en oeuvre un mécanisme de préemption qui correspond dans le cas simple à une arité fixe. Se pose alors la question suivante, à quoi correspond un appel sur une liste de donnée inférieur à tout modèle de satisfaisabilité ? Il y a deux conceptions possible, soit on considère que c'est une autre fonction non encore définit, ou soit on applique la curryfication, une règle plus simple qui n'occasionne pas perte d'information consistant à remplaçer le role de la fonction qui n'a pas assez d'arguments par la fonction curryfiée.

Exemple d'application de la règle de curryfication : considéron une fonction ternaire `f(".,.,.")` que l'on applique à une donnée `x` de taille un. Le terme `f(x)` retourne la fonction binaire qui appliquée à `(y,z)` de taille 2 retourne `f(x,y,z)`. Et si elle était applique à une donnée `(x,y)` de taille 2, le terme `f(x,y)` retourne la fonction unaire qui appliquée à `z` de taille un, retourne `f(x,y,z)`. Pour résumer : Quelque soit des termes `x,y,z` de taille 1 quelque soit une fonction `f` d'arité supérieur ou égale à 3, nous avons :

`f(x,y)(z) = f(x,y,z)`
`f(x),(y,z) = f(x,y,z)`
`f(),(x,y,z) = f(x,y,z)`
`f(x)(y)(z) = f(x,y,z)`
`f()(x)()(y)()()(z) = f(x,y,z)`

 

---- 29 août 2025 ----

 

 

 

 

 

 

 

 

 

 

axiome introduit deux notions, celle de construction de nouveau terme par emboitement et celle d'évaluation ou d'éxecution d'un programme. Ainsi par exemple, le programme `f` appliqué à la liste de données `(f,f,f)`, forme un nouveau terme par emboitement de termes noté `f(f,f,f)`. Et ce terme s'évalue en une liste de termes qui peut être par exemple `(f, f(f), f(f)(f), f, f(f,f), f, f)` qui est le résultat de l'exécution du programme, ce que produit le programme `f` appliqué à `(f,f,f)`.

 

La recherche du langage de programmation idéal nous amène au premier abord dans une structure fondamentale qu'est l'algèbre libre. Au départ, il n'y a pas de distinction entre programme et donnée, et on part d'une unique donnée notée `f`, qui est donc un programme pouvant s'appliquer sur une liste de données pour produire une liste de données. Et chaque donnée produite est donc également un programme pouvant s'appliquer à son tour sur une liste de données pour produire une liste de données, etc.... Le langage doit pouvoir noter ces différentes programmation sans les exécuter. Cela se fait par emboitement, en accolant le terme jouant le rôle de programme à la liste de termes jouants le rôles d'argument. On construit ainsi l'algèbre libre engendrée par un élément-opérateur `f`. Cela se traduit par les deux axiomes suivants :

1. `f` est un terme.

2. Tout terme peut s'appliquer à une liste de termes pour produire une liste de terme

3. Tout terme s'évalue en une liste de termes.

On note l'ordre du terme résultat par un indice commençant par zéro. Ainsi `f_2(f)` désigne le troisième terme résultat du calcul `f(f)`. Mais il se peut qu'il n'y ait pas de troisième terme résultat, ce qui est représenté par la valeur symbolique `sf"nada"`.

A ce stade, nous ne considérons aucun démon. Le programme ne démarre que lorsque les données ont été mises à disposition, et ne transmet le résultat qu'une fois le calcul achevé. Notez que la mise à disposition des données à une fontion ne signifie pas que la fonction va prendre toutes les données, elle ne prendra que les premières données qui correspondent à son premier shéma d'acceptation rencontré. Les données restantes sont alors disponnibles pour la suite d'un procédé plus globale tel que celui mis en oeuvre dans la logique polonaise.

Considérons une variante de l'axiome n°2 notée n°2' :

2'. Tout terme peut s'appliquer à une liste de termes pour former un terme.

Si nous remplaçons l'axiome n°2 par l'axiome n°2', il n'est plus nécessaire de recourir aux indices. On simplifie ainsi l'écriture des termes. Et le domaine de calculabilité n'en n'est pas réduit pour autant, en revanche la capacité d'optimisation de la complexité en est réduite. Exemples de termes :

`f, f(), f(f), f(f,f), f(f(),f)(f)`

Comme une fonction peut ne rien retourner, on ajoute le terme `sf"nada"` avec comme règles que pour toutes listes `"*"x, "*"y`, tout terme `h` appliqué à `("*"x,sf"nada","*"y)` retourne ce que retourne `h` appliqué à la liste `("*"x, "*"y)`. En particulier `h` appliqué à `sf"nada"` retourne ce que retourne `h` appliqué à la liste vide. Puis nous verrons plus tard à quoi correspond la fonction `sf"nada"`.

`AAh, AA"*"x, AA"*"y, h("*"x,sf"nada","*"y)=h("*"x,"*"y)`

 

 

 

 

 

13) La logique polonaise

 

 

Puis, pour ne pas distinguer deux sortes d'objet sur lequel peut s'appliquer une fonction, on ajoute les axiomes suivants :

  1. Toute liste applatie non-vide de termes est un terme.
  2. Les listes sont applaties.
  3. Les listes singletons sont indentifiées au terme qu'elle contient.

Les listes de termes sont des termes donc pouvent être membre d'une liste de terme (concaténation), et peuvent s'appliquer à une liste de termes. Exemples :

`f(f)`, `f(f,f)`, `(f,f)(f,f)`, `(f,f)(f,f,f)(f)`

On étend ainsi l'algèbre en considérant les listes de termes applaties et concaténées comme des termes, cela revient à ajouter une opération binaire asscociative dite de produit `×`. Voir exemples :

`a×b = (a,b)`
`a×(b×c) = (a,b,c)`
`(a×b)×c = (a,b,c)`
`(a×b)×(c×d) = (a,b,c,d)`

La structure peut alors être définie avec juste deux lois binaires : le produit `×` pour construire les listes, et l'opération d'application de priorité syntaxique plus forte, et que l'on note de deux façons : `^` et app noté par un espace blanc :

En notation française, la fonction est placée en exposant à droite de l'argument :

       `x^y ≡ y(x)`

et la priorité d'évaluation est à gauche d'abord, faisant que :

       `{:a^b:}^c = (a^b)^c ≡ c(b(a))`

En notation anglaise, la fonction est juxtaposée à gauche de son argument séparé par un espace blanc

       `y x ≡ y(x)`


et la priorité d'évaluation est à droite d'abord, faisant que :

       `c b a = c (b a) ≡ c(b(a))`

 

Mais quel rôle de fonction pouvons-nous donnez à un couple de fonctions telle que `(f,g)` ? Il n'y a pas de perte de capacité d'expression du langage à définir un rôl particulier. Deux premiers rôles semblent apparaître qu'est la composition série mettant en oeuvre une forme de logique polonaise, et la composition parallèle.

La composition aprallèle : L'application d'une liste de fonction sur une liste d'arguments `"*"x` consiste à appliquer chaque application à la mâm listes d'argument et de retourner la liste des résultat ainsi constitué. Ainsi, quelque soit une listes de termes `u,v,w,...` et quelque soit une autre liste de terme `x,y` nous avons :

`(u,v,w,...)(x,y) = (u(x,y),v(x,y),w(x,y),...)`

La composition série : La liste des arguments est utilisée dans l'ordre pour satisfaire la demande d'argument de la liste des fonctions.

 

 

 

 

 

Il n'y a pas de perte de capacité d'expression à considérer qu'une liste de n fonctions correspond à la fonction calculant le n-uplet résulat des n fonctions appliquées aux mêmes arguments. L'application d'une liste de fonction sur une liste d'arguments `"*"x` consiste à appliquer chaque application à la mâm listes d'argument et de retourner la liste des résultat ainsi constitué. Ainsi, quelque soit une listes de termes `u,v,w,...` et quelque soit une autre liste de terme `x,y` nous avons :

`(u,v,w,...)(x,y) = (u(x,y),v(x,y),w(x,y),...)`

La structure ainsi déifnie se résume par cette axiomatique :

Deux lois binaires `×` et `^`

`×` est associatif : `a×(b×c)=(a×b)×c`

`^` est prioritaire sur `×`

app est prioritaire sur `×`

`^` est associatif à gauche : `{:a^b:}^c = (a^b)^c ≡ c(b(a))`

app est associatif à droite : `c b a = c (b a) ≡ c(b(a))`

`^` est distributif à droite sur le produit `×` : `a^(f×g) = a^f×a^g`

app est distributif à gauche sur le produit `×` : `(f×g) a = f a×g a`

13) la currification

 

 

11) L'interpréteur

Notre langage est interprété par un interpréteur. Lorsque l'on soumet un terme à l'interpréteur, celui-ci va l'évaluer relativement à un contexte en cours contenant la grammaire, et retourner un terme résultat. On notera `X↬Y` avec une priorité syntaxique la plus faible de toute, pour indiquer que l'évaluation du terme `X` par l'interpréteur retourne le terme `Y`. La forme fonctionnelle de cet opérateur se note `sf"evaluar(...)"`. Ainsi l'interpréteur est une fonction, `sf"evaluar"(X)=Y`

Tout terme est un élément et tout élément est une fonction. Il n'y a pas de différence de nature entre donnée et programme. La variable joue un rôle central, elle possède un contenue. L'évaluation d'une variable consiste à retourner son contenu. La variable peut être une fonction dans ce cas son contenu est le programme de la fonction. Qu'est ce qui différencie un contenu de type donnée d'un contenu de type fonctionnelle, c'est uniquement la présence d'instructions définies dans le langage de programmation en cours.

Une donnée `f`, si elle n'est pas définie, elle contient l'élément `sf"indef"`, alors elle se comportera comme suit : Lorsque l'on demandera son évaluation elle ne retournera pas sa valeur `sf"indef"` mais retournera elle-même, et représente ainsi une variable libre susceptible de s'unifier à toute donnée. Sa valeur se changera en `sf"gratis"` ce qui signifie qu'elle est défini comme étant libre. Et lorsque l'on redemandera son évaluation elle ne retournera pas sa valeur `sf"gratis"` mais retournera elle-même,

`f↬f`

Une donnée `f` appliquée à une séquence quelconque de termes `"*"x` servant d'entrée, se note `f("*"x)`. Si la fonction `f` n'est pas défini ou bien est libre, c'est à dire si `f` contient l'élément `sf"indef"` ou `sf"gratis"`, lorsque l'on redemandera son évaluation elle ne retournera pas sa valeur mais retournera elle-même. Lorsque l'on demandera l'exécution de son programme avec comme entrée la séquence `"*"x`, elle retournera comme résultat le terme `f("*"x)`, sans évaluer ni `f` ni `"*"x`, et représentera ainsi une fonction libre susceptile de s'unifier à toute fonction.

`f("*"x) ↬ f("*"x)`

Mais détaillons l'évaluation de `f("*"x)`. Cette expression comprend deux variables `f` et `"*"x`. Et on applique l'une sur l'autre, ce qui peut se noter en utilisant l'application d'application notée `"app"(".,...")`, c'est dire par l'expression `"app"(f,"*"x)`.

Lorsque l'on évalue le terme `f("*"x)` on commence par évaluer la variable `f` qui étant libre retourne `f`, puis on l'applique à la variable `"*"x` ce qui produit le terme `f("*"x)` `"*"x` n'est pas évalué, car c'est le progrmme `f` qui décide s'il faut évaluer ou pas ses arguments, et comme `f` est une variable libre, l'application d'une variable libre sur quelque chose retourne le terme composé sans rien évaluer.

Il en est de même si `f` contient une donnée qui n'est pas un programme. Dans ce cas, la fonction ne peut pas se calculer et elle retourne le terme d'appel `f("*"x)`. En revanche, si `f` contient un programme (c'est à dire, une donnée obéïssant à la syntaxe du langage de programmation en cours), alors `f` est évalué, son argument `"*"x` est évalué, et le programme est exécuté et retourne un terme résultat.

 

 

 

 

 

 

12) Composition série

Comme il n'y a pas de différence entre donnée et programme, évaluer une donnée consiste à exécuter un programme. La donnée représentée par le terme `x` est donc un programme. Et le résultat de son exécution dépend de l'état du système, ou plus précisément d'un ensemble de variables appelé la base de `x`. C'est une partie du contexte.

Les variables, qui jouent un rôle paradigmatique, sont caractérisées par un état qui est une caractérisation plus complète que leur seul contenu. Le contenu d'une variable ne désigne que le terme auquel la variable permet d'accéder, alors que son état caratérise complètement la variable dans le système.

Le contexte représente l'état du système, c'est à dire l'état de toutes les variables. L'évaluation d'une donnée, c'est à dire l'évaluation d'un terme `x`, ne va pas seulement produire un terme résultat, mais va modifier l'état du système. Cette modification du système est appelé l'effet de bord, et il couvre un ensemble de variables suceptibles d'être modifiées par l'évaluation du terme en question, et que l'on appelle le bord de `x`, et qui constitue une partie du contexte.

Si des termes `x,y,z` ont des bords disjoints de chaque base et de chaque bord, ils peuvent être évalués dans n'importe quel ordre, l'état du système sera le même à la fin. Par contre si le bord de `x` n'est pas disjoint de la base et du bord de `y` alors l'exécution de `x` puis de `y` pourra ne pas avoir le même effet que l'exécution de `y` puis de `x`.

On utilise le point-virgule pour composer en série les programmes. Ainsi, l'évaluation de `x";"y";"z` procédera à l'évaluation de `x` puis de `y` puis de `z`. L'évaluation d'un terme produit un terme. Les résultats intermédiaires ne sont pas gardés, seul celui obtenu par le dernier calcul est retenu et fait office de résultat.

Si on souhaite appliquer la composition série à une séquence de programmes `x,y,z`, on utilise la fonction `sf"evaluar_en_serie"(x,y,z)`.

`sf"evaluar_en_serie"(x,y,z) = x";"y";"z`

L'appel `f(x,y,z,"*"u)` ne présage pas si les termes `x,y,z,"*"u` vont être évalués ou non ou même plusieurs fois, ni dans quel ordre ils vont être traités. Certaines fonctions peuvent ne pas évaluer certains termes d'entrées et prendre leurs expressions telles quelles sans les évaluer. D'autre fonction vont évaluer plusieurs fois leurs arguments. C'est la fonction `f` qui va déterminer les traitements et l'ordre des traitements de ses arguments. Par contre l'appel doit être formalisé en une séquence `(x,y,z,"*"u)` transmise à la fonction `f(...)` avant qu'elle ne commence son traitement.

Si au lieu d'un tel appel nous avons simplement une séquence de termes `a,b,c`, que se passe-t-il ?. L'évaluation de la séquence par l'interpréteur sécrit :

`sf"evaluar(a,b,c)"`

Les termes `a,b,c` sont évalués par la fonction `sf"evaluar"` qui évalue dans l'ordre chaque terme de la séquence `(a,b,c)` et retourne le résultat sous forme de séquence `sf"evaluar"(a),sf"evaluar"(b), sf"evaluar"(c)`. Tandis que `x";"y";"z` correspond à `sf"evaluar_en_serie"(x,y,z)` et ne retourne comme résultat que le dernier terme évalué..

13) Composition parallèle

Unix a inventé le concept de processus avec son environnement propre, et le concept d'exécution en arrière plan. A chaque fois qu'il crée un processus, il duplique le contexte du processus parent pour en donnée un au fils, autrement-dit, il fait une copie de l'état du système qu'il met à disposition du nouveau processus.

On note `x#` la création d'un processus pour calculer `x` en arrière plan. Une fois `x` calculé, le contexte obtenu et abandonné, seul le résultat est gardé `sf"evaluar"(x)"`. On note `x#y` la composition parallèle. Le programme `x` est calculé en arrière plan et le programme `y` est calculé au premier plan, tout deux à partir d'un même contexte initial qui a été dédoublé, mais le contexte résultant sera celui modifié par `y`, car celui modifié par `x` n'est pas gardé, seul le résultat est gardé. Le résultat sera la séquence des deux résultats. Ce faisant, l'instruction est identique à `x#,y`. Et si on, ne souhaite pas modifier le contexte, on notera `x#y#`. Les exécutions de `x` et de `y` se feront à partir d'une copie du contexte, et seul leurs résultats seront gardés et présenté sous forme de séquence.

Si on souhaite évaluer en parallèle les termes `a,b,c`, on notera `a#b#c` (ou `a#,b#,c`). Cela veut dire que l'évaluation de `a` de `b` et de `c` se font à chaque fois à partir d'un même contexte initiale. Puis `a` et `b` sont exécutés en arrière plan dans des processus isolés où le conctext final n'est pas gardé, seul les résultats sont gardés et mis dans une séquence.

L'opérateur point-virgule, appelé l'opérateur de composition série de programmes, aura une priorité synaxique plus faible que celle de l'opérateur dièse, appelé l'opérateur de composition parallèle de programmes. Exemple :

`a";"b"#"c";"d`

L'évaluation de ce terme évalue en parallèle `a";"b` et `c";"d`. Les effets de bord de `a` peuvent modifier l'évaluation de `b` et les effets de bord de `c` peuvent modifier l'évaluation de `d`. Mais l'évaluation de `a";"b` n'aura pas d'effet de bord sur l'évaluation de `c";"d`. Le résultat sera une séquence de deux termes, et le contexte final sera celui créé par `c";"d`

La séquence non-singleton n'étant pas un terme, l'opérateur virgule aura la priorité la plus faible.

14) Contexte partiel

Dupliquer le contexte est une opération lourde. C'est pourquoi il est pertinent de concevoir la notion de contexte partiel, désignant une partie du contexte c'est à dire un ensemble de variables, une séquence de variables. Etant donné un tel contexte partiel noté `"*"v`. On redéfinie la notion d'éxecution parallèle en le limitant à ce contexte partiel. Le programme `x#_("*"v)` est executé dans un sous contexte où toutes les variables de la séquence `"*"v` sont dupliquées et créées comme variables locales. L'exécution de `x#_("*"v)y` s'exécute en parallèle pour les variables de la séquence `"*"v`, et s'exécute en série pour les autres variables.

15) Flux synchrone et asynchrone

Un flux de termes peut ne pas avoir de fin et donc être de taille infinie, en revanche une séquence ou un terme doit être de taille finie.

La séquence à vocation à regrouper les arguments d'entrée d'une fonction sur laquelle un mécanisme d'unification peut s'appliquer, tandis que le flux de données qui peut être sans fin va voir son premier terme traité par le démon comme un argument dès l'établissement de la connexion du flux.

La calculabilité met en exergue deux concepts importants qu'est la séquence de données représentée par la variable muette `"*"x` et qui est suceptible de s'unifier à d'autres séquences, et le flux de données représenté par la variable muette `©x` qui peut être sans fin et où le taitement des données commence dès l'établissement de la connexion du flux, celui-ci présentant son premier terme comme un argument d'entrée.

Les flux ou canaux sont des éléments essentiels de programmation, c'est pourquoi on accroit la puissance de programmation en les identifier à des éléments ordinaires avec toutes les commoditées qui les accompagnent, sur lesquels on ajoutera par dessus les méthodes spécifiques aux canaux. La variable désignant le flux se comporte comme une variable contenant le premier terme du flux.

Mais un flux peut être vide. Apparait donc un terme spécifique pour indiquer un flux vide. C'est le terme désignant rien, noté `sf"nada"` ou de façon court par le symbole `□`. La plus part du temps les flux se terminent. Apparait donc un autre terme spécifique pour indiquer la fin du flux de termes. C'est le terme de fin-de-transmission. On le notera `∎`. Ces deux termes sont associés à des mécanismes d'exception.

Le flux peut-être synchrone ou asynchrone. S'il est synchrone alors il forme un mécanisme rigide de transmission de termes, et il n'est jamais vide, il bloque le processus de lecture jusqu'à l'arrivé d'un terme que le processus lit. Parcontre s'il est asynchrone alors il forme une file de termes, et il peut être momentanément vide.

La façon dont les sous-programmes peuvent être interconnectés par des flux constitue une sorte de topologie qui découle de la vision fractale du programme. Le flux est un canal reliant une sortie de type flux d'un programme à une entrée de type flux d'un programme.

Le flux peut aussi être convertie en une séquence, à condition qu'il se termine.

Le canal peut avoir plusieurs entrées, cela signifie qu'il y a plusieurs processus qui peuvent émettre et qui vont ainsi intercalé leur émission dans le canal, avec comme règle de priorité "le premier processus demandant d'émettre, d'abord". Le canal peut avoir plusieurs sorties, cela signifie qu'il y a plusieurs processus d'écoute qui vont pouvoir capturer des termes arrivants par ce canal, avec comme règle de priorité "le premier demandant la capture, d'abord".

Et le flux peut être cloné plusieurs fois en des flux synchrones ou asynchrones, c'est à dire former plusieurs canaux ayant les mêmes sources mais pouvant avoir d'autres processus d'écoutes et obéissant aux règles du synchronisme ou de l'asynchronisme.

Le flux synchrone bloque l'émetteur, et il peut y avoir plusieurs émetteurs, tant que le récepteur n'a pas retiré le terme transmis, et il peut y avoir plusieurs récepteurs. Chaque flux cloné en un flux synchrone bloque les émetteurs, celui-ci devra attendre que l'un des récepteurs est retiré le terme transmis, et ceci pour chaque clone synchrone du flux.

Le flux asynchrone ne bloque pas les émetteurs, mais la file de termes que représente le flux peut s'accroître considérablement et dépasser la taille mémoire disponible. Pour éviter cette erreur, le flux asynchrone comprend un mécanisme de retour d'information au près des émetteurs sur la taille du flux et également de chacun de ses clones asynchrones

Le flux synchrone bloque le récepteur, et il peut y avoir plusieurs récepteurs, tant qu'un émetteur n'a pas envoyer de terme à transmettre, et il peut y avoir plusieurs émetteurs. Chaque flux cloné en un flux synchrone bloque les émetteurs, celui-ci devra attendre que l'un des récepteurs est retiré le terme transmis, et ceci pour chaque clone synchrone du flux.

Le flux asynchrone ne bloque pas les récepteurs, c'est pourquoi la valeur du flux vide, noté `sf"nada"` ou de façon court par le symbole `□` est possible.

Nous avons plusieurs types de variable :

Et dont le principe est qu'elles puissent être unifiés avec d'autres données de même type avec une complexité linéaire.

La valeur de ©z à un instant donné est le terme placé en tête du flux ©z, qui peut être `□` si le flux est asynchrone et qu'il est vide, et qui peut être `∎` si le flux est terminé. La tête du flux joue le rôle d'une variable d'entrée de type terme.

16) Branchement synchrone ou asynchrone à un flux

Il est possible de simplifier le concept de flux, en reportant le comportement synchrone ou asynchrone, non-pas au flux, mais à la connexion à un flux. On note le canal de deux façons différentes selon que la connection évoquée est synchrone ou non, `©z, "®"z`. Dés lors, la lecture `®z` ainsi que l'écriture `®z":="... ` sont bloquantes, tantis que la lecture `©z` ainsi que l'écriture `©z":="... ` ne sont pas bloquante et peuvent manipuler la valeur vide, `"□"`. Un flux synchrone précédement décrit correspondra à un flux dont toutes les entrées et toutes les sorties sont synchrones.

 

Précédent
Suivant

 


Dominique Mabboux-Stromberg
juin 2025