Précédent

Récursivité non-déterministe

1) Introduction

...

2) Opérateur unaire

On entend par opérateur unaire, sous-entendu interne, une application d'un ensemble de départ `E` vers lui même. On note cette opérateur `f(".")`. Le suffixe occasionnel `(".")` indique l'arité de l'opérateur. Le symbole "." indique l'ensemble sous-jacent de la structure `E` qui est l'ensemble de départ de `f`.

`f in (E->E)`

Lopérateur est rendu non-déterministe si au lieu de retourner un élément, il en retourne plusieurs. (Si le nombre d'élément retournés est toujours fini, on parlera d'opérateur non-déterministe fini.)

`f in (E->ccP_">0"(E))`

On redéfinit l'opérateur en l'appliquant aux sous-ensembles non-vides de `E` comme suit :

`f (A) = uuu_(a in A) f(a)`

`f in (ccP_">0"(E)->ccP_">0"(E))`


 

 

7) L'aspet fractal d'un programme

Tel un fractal, un programme, s'il est suffisament complexe, comprendra des sous-programmes qui se comportent comme des programmes. Un programme contiendra des sous-programmes, c'est à dire ayant une entrée, et une sortie. La programmation consistera alors à raccorder les différentes sorties aux différentes entrées de ces différents sous-programmes. Cela permet de mettre en oeuvre la pile des appels avec un contexte distinct pour chaque appel d'un sous-programme.

Un programme peut ne pas s'arréter et s'exécuter en parallèle avec d'autres programmes. Il comprend alors deux types d'entrée/sortie particulièrement simples que sont celles transmises avant le début de son exécution comme entrée ou à la fin de son exécution comme sortie à travers des médiums appelés variables d'entrée/sortie, et celles transmises en cours de route à travers des médiums appelés flux d'entrée/sortie.

Puis il comprend une autre entre/sortie plus complexe que sont les graphes partagés qu'il explore et modifie. Et cela comprend non seulement les graphes mais toute structure de données partagées par plusieurs sous-programmes.

Le programme se décompose en sous-programmes qui peuvent s'éxecuter en parallèles et qui sont reliés entre-eux par des variables d'entrés/sortie et des flux d'entré/sortie. L'algorithmie se contente de ces deux modes. Reste à trouver la meilleur notation. Les programmes ou sous-programmes sont des blocs de codes, c'est à dire des n-uplets d'instruction, c'est pourquoi nous utiliserons les parenthèses `( )` pour encadrer un bloc de code et non les crochets `{ }` que l'on réserve au énumération où l'on peut permuter deux éléments quelconques.

Les noms des variables globales et de flux globaux occupent l'espace de noms du programme principale. L'aspect fractal se manifeste également dans les espace de noms, faisant que des espaces de noms existe à tous les niveaux, et qu'ils héritent des espaces de noms parent.

8) Les variables de sortie

Le bloc de code est un `n`-uplet d'instructions qui commence éventuellement par énumérer les variables d'entrée suivit par le symbole "|" puis par énumérer les instructions suivit par le symbole "|" puis par énumérer les variables de sortie. Exemple `(A,B|...|C,D)`. Les variables de sortie sont identifiées à l'échelle d'une strate et de doivent apparaitre comme variable de sortie qu'une seul fois. Avec cette règle, toutes variables de sortie définie une composition arborescente unique de sous-programmes aboutissant à son calcul.

9) Les flux

Les flux ou canaux sont des éléments essentiels de programmation. Ils sont identifiés globalement par une lettre préfixé par l'un des symbole `"།"z, "༐"z`, selon que la conexion est synchrone ou asynchrone. On accroit la puissance de programmation en les identifier à des éléments ordinaires avec toutes les commoditées qui les accompagnent, sur lesquels on ajoute 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.

Un flux connecté de façon asynchrone 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 canal possède deux modes d'écriture, un mode d'écriture synchrone, et un mode d'écriture asychrone. Et deux modes de lecture, la lecture et la prévue.

Si il y a plusieurs demande d'écritures synchrones, elle sont mise dans une file d'attente. Et les demandes d'écritures asynchrones passe devant les demandes d'écriture synchrone bloquée pour être tout de suite exécutées.

Si le canal n'utilise pas d'écriture asynchrone, il est dit synchrone et d'implémentation plus simple. Par contre si le canal est utilisé avec des écritures asynchrones, il se comporte comme une file, une mémoire tampon, capable d'enfiler un grand nombre d'éléments.

Un canal peut être cloné plusieurs fois. On le note en metttant un indice `"།"z, "།"z_0,"།"z_1,"།"z_2`

10) Gestion d'évènements

La lecture ainsi que la prévue peuvent tomber sur `sf"nada"` indiquant que le canal n'a pas encore envoyé de données, et sur `"∎"` indiquant une fin de transmission. On prédéfinit quatres gestions d'évènement que sont la fin de transmission d'un canal, l'activation d'un canal, le dépassement d'une quantité de mémoire d'un canal, la lecture d'un élément particulier sur un canal :

Ces gestionnaires peuvent être placé à plusieurs endroits. Lorque l'évènement se produit, le signal est capturer par les blocs de code en exécutions, puis successivement par les blocs de codes parents et ainsi de suite.

11) Structure de données partagées

Deux variables `x,y` peuvent être égales `x"="y`, mais davantage elles peuvent désigner la même valeur `x"≡"y`. Puis certaines parties de l'élément `x` peuvent être partagés. Le cas générale s'appelle un terme, et certaines parties du terme peuvent être partagé avec d'autres termes.

12) Conception d'algorithme

On définie des priorités et autres mécanismes syntaxiques dans le but de réduire l'usage des parenthèses, dont on ne sait pas encore très bien la forme que cela va prendre. Le but consiste à réduire la taille des algorithmes formels en les constituant en un emboitement d'opérations arithmético-informatiques fondamentales.

.

---- 27 février 2026 ----

 

 

 

 

Si on veut utiliser les arcs du graphe, on remplace simplement l'instruction :

`"por" y "∈" x".hilos", y "∉" E  => y"≫"E,  y"≫"F`

par :

`"por" p "∈" x".arco", p".s""∉" E  => p".s""≫"E,  p".s""≫"F`

L'algorithme peut s'écrire littéralement :

1.  Fonction EXPLORER-GRAPHE(D) où D est un ensemble de noeuds.
2.  Initialise l'ensemble des noeuds connus égale à D.
3.  Initialise la frontière égale à D.
4.  Faire en boucle :
          1.  Si la frontière est vide alors arréter et retourner l'ensemble des noeuds connus.
          2.  Choisir un noeud dans la frontière.
          3.  Pour chaque noeud fils, s'il n'est pas déjà connu.
                    1.  Ajouter-le dans la frontière.
                    2.  Ajouter-le dans l'ensemble des noeuds connus.
          4.  Retirer le noeud choisi de la frontière.

8) Algorithme de recherche des chemins les plus courts

On munie chaque arc `p` d'un attribut de distance, `p".dist"`. Et on souhaite rechercher les chemins les plus courts partant d'un noeud `r`. De tels chemins peuvent être multiples si les distances sont égales, et forme un sous-graphe couvrant tous les noeux.

Il n'est pas nécessaire de mémoriser tous les arcs qui constitue les chemin les plus courts. En effet, si l'on connait les disance minimale de la racine pour chaque noeud, alors à partir de n'importe quel noeud, pour retrouver les chemins les plus court, on choisie les noeuds fils dont la distance minimale est égale à la distance minimal du noeud père plus la distance de l'arc emprunté. Cette remarque simplifie le problème au seul calcul des distances minimales.

Une approche simple consiste souvent à mémoriser le résultat dans le graphe lui-même. On munit les noeuds `x` d'un attribut supplémentaire mémorisant la distance au noeud `r` la plus petite connue, `x".dist"`. Cet attribut est initialisé à `x".dist="∞` sauf pour le noeud `r` qui vaut `r".dist="0`.

8.1) Conception de l'algorithme

La structure de données étant posée, voyons comment nous pouvons déduire un algorithme presque naturellement en le construisant par tâtonnement. On part d'un exemple générale que l'on essaye de résoudre.

On explore le graphe à partir de la racine `r`. Ainsi la frontière `F` vaut initialement `F"="{r}`. L'exloration d'un noeud `x` consiste à explore chaque arc partant de `x`. Pour un arc `p` partant de `x`, on ajoute la distance minimum connu de `r` à `x` mémorisé dans `x".dist"` à la distance de l'arc `p".dist"` et on compare cette nouvelle distance avec la distance minimum connu du noeud d'arrivé de l'arc `p".s.dist"`. Si elle est supérieure ou égale alors elle ne modifie pas la distance minimal connue. Par contre, si elle est inférieure, alors l'arc fait un raccourci et modifie la distance minimal connu du noeud d'arrivé de l'arc. Les conséquences de cette modification sont traités simplement en réexplorant le noeud. Donc nous appliqu'on la règle suivante :

`(x".dist"+p".dist" < p".s.dist")    =>    (p".s.dist"= x".dist"+p".dist")`

Comme le noeud dont la distance minimum a été modifiée, a été placé dans la frontière, les conséquences de cette révision de la distance minimum seront assurément traitées. L'algorithme s'arrète lorsque `F` est vide.

`{r |`
       `"por" x"∈"r".grafico.nodo", x".dist="oo`
       `r".dist =" 0`
       `"var" F"="{r}`
       `AA x "∈" F,"por" p "∈" x".arco", (x".dist"+p".dist" < p".s.dist") => (p".s.dist" ≔ x".dist"+p".dist", p".s" "≫"F), x"≪"F`
`}`

L'algorithme calcul les distances minimales qu'il stoque dans l'attribut `"dist"` des noeuds du graphe. Donc il ne retourne rien et fait une modification des attributs `"dist"` des noeuds du graphe.

9) Les base de l'algorithmie

C'est l'arithmétique qui engendre les algorithmes comme elle engendre la logique du premier ordre. On propose d'opter pour une notation des algorithmes prétendument plus universelle, comme l'est la notation des formules de logique du premier ordre. Et nous procédons presque par duplication, en s'inspirant de la résolution dialectique d'une proposition du premier ordre transformant la proposition en un algorithme de recherche de résolution. La notation d'exploration logique est la suivante :

Si le code du programme et écrit avec ce langage et ne se modifit pas en cours de route, et si les ensembles `E` utilisés sont finis et fixes, alors l'arrêt du programme est sûre (il n'y a pas de boucle sans fin). Deux modes d'appelle sont alors possible. Le premier mode, dit de test, est pour juste savoir si l'action est possible. Dans ce mode, après l'exécution du programme, toutes les modifiations sont annulées et seul l'éventuel signal d'échec est remonté. Le second mode dit d'action, execute le programme. Cependant si le programme émet un signal d'échec, aucune action n'aura eu lieu et les deux modes coïncideront

La situation se complique lorsque l'on autorise la modification des ensembles `E` en cours d'exécution. Le choix de l'ordre dans lequel parcours les indexes devient déterminant.

10) Une approche plus basique

La description précédente est trop éloignée des opérations informatiques élémentaires. On peut donc proposer une autre approche plus classique de construction du langage à partir de ces opérations et objets jugés fondamentaux et parmi lesquels on trouvera l'ensemble, l'énumérateur, la liste, le graphe, etc., mais, en intégrant une partie de ce qui précède de manière plus souple. On ne journalise pas tout, seuls certains blocs, appelés blocs tests.

De la même façon qu'un graphe peut ne pas exister en mémoire mais être bien défini par son interface de commande où les noeuds du graphe correspondent à des états d'un système et les arcs correspondent à des actions (transformation du système), une liste peut ne pas exister en mémoire (en particulier si elle est infinie) et être bien défini par son interface de commande. C'est ce que l'on appelle un énumérateur. Pour l'algorithme, les deux objets (liste et énumérateur) ayant le même interface, la seule distinction réside dans la complexité différente de certaines opérations.

 

---- 23 février 2026 ----

 

 

 

 

Les blocs de code peuvent émettre des flux et lire des flux. Et il existe un flux d'entrée standart et un flux de sortie standart pour chaque bloc de code. L'ajout d'un élément `x` dans le flux de sortie standart se note `x"≫"`. La lecture d'un élément du flux d'entré standart se note `"≫"x`.


 

Un bloc de code formant une boucle que l'on doit exécuter indéfiniment est préfixé par le symbole `oo`.

L'instruction qui met fin au bloc de code avant la fin et retourne ce qui suit, se note par le symbole `➥`.