Prof de TP : gabriel.scherer@irif.fr

Programmation synchrone

Système réactif

Heptagon installé : heptc

Ce qu’on a appris jusqu’ici

Programmation réactive

reactive

Exemple réactif

Caractéristiques essentielles des systèmes réactifs

Dans le logiciel critique, il faut faire “ceinture et bretelle”.

echantillonage

Correction temporelle \leftrightarrow correction fonctionnelle \RA il y a des cas où ça va ensemble

Mathématiques nées avec la révolution industrielle

Exemple : Régulateur à boules de Watt

Langages synchrones

Langages spécialisés pour la programmation des systèmes réactifs

Objectifs

\RA Idée sous-jacente : Notion de temps logique, discret et global

Langages conçus en France : à Grenoble (notamment), Nice, Rennes et Paris.

Schéma-bloc

Utilisés pour palier les besoins des ingénieurs avec un background “automatique” vers le code C

schema-bloc

Heptagon : la programmation synchrone a flots de données

Cf. Dossier de code

heptagonExécution cyclique à 3 phases :

abstraction
A la fin de développement : vérification des contraintes temporelles.

On programme dans un langage avec un temps logique et un temps discret.

On considère qu’entre le début et la fin du cycle, aucun temps ne s’est écoulé, afin que l’environnement n’ait pas le temps de changer.

D’un certain point de vue, on peut représenter un programme synchrone comme une machine à état / un automate (à mémoire) finie et déterministe. Notre automate, en plus d’avoir des entrées, possède des sorties, c’est un transducteur.

Pour prouver si deux automates sont égaux, on peut faire une bisimulation.

Une bisimulation est une relation RS×SR \subseteq S \times S tel que (s1,s2)R\forall (s_{1}, s_{2}) \in R, on a :

  1. s1s_{1} final \Leftrightarrow s2s_{2} est final
  2. Si s1as1s_{1} \xrightarrow{a} s_{1}', il existe s2s_{2}' tel que s2s2s_{2} \xrightarrow{s_{2}'} et (s1,s2)R(s_{1}', s_{2}') \in R
    part2
  3. si s2as2s_{2} \xrightarrow{a} s_{2}', il existe s1s_{1}' tel que s1as1s_{1} \xrightarrow{a} s_{1}' et (s1,s2)R(s_{1}', s_{2}') \in R

Théorème : deux états finaux s1s_{1} et s2s_{2} reconnaissent le même langage ssi il existe une bisimulation RR telle que (s1,s2)R(s_{1}, s_{2}) \in R

Exemple : automate
Bisimulation : R={(x,u)x,(y,v)x,(y,w)x,(z,w)x,(z,v)x}R = \{ (x, u)_{x}, (y, v)_{x}, (y, w)_{x}, (z, w)_{x}, (z, v)_{x} \}

Exemple transducteur :
transducteur

Heptagon

Langage fonctionnel à flux de données

\RA produits des automates

node identite(x : int) returns (o : int)
let
o = x;
tel

Nœud = fonction de flots

Compilation

Fichier .ept hept\xrightarrow{hept} fichiers .c gcc\xrightarrow{gcc} exécutable

heptc -target c -s identite test.ept

heptc -i -target c -s identite test.ept :

val identite(x : int :: .) returns (o : int :: .)

identite. intωintω \text{identite. } int^{\omega} \ra int^{\omega}

Monde simple, les objets définis sont utilisés.

node identite(x : int) returns (o : int)
var y : int
let
o = x;
tel

y doit être utilisé.

Heptagon n’est pas un langage impératif, on définit des équations.
On fait des conjonctions (définitions simultanées), l’ordre n’est pas important ; tout est vrai.

Insensible à l’ordre des équations
\RA Définitions mutuellement récursives

Dans un programme Heptagon valide, on peut toujours calculer une valeur. Rejette les programmes non causaux (le compilateur fait une analyse de causalité/productivité).

y = y (* erreur de causalité *)

Programmation Flots-de-données et causalité

Opérateurs combinatoires

node f() returns (x, y : int, z : bool)
let
x = 1;
y = 42;
z = false;
tel
node g() returns (o: int)
let
(* motif *)
(x, y, z) = f();
o = x;
tel

Equivaut à :

0 1 2 3 4
x 1 1 1 1 1
y 42 42 42 42 42
z False False False False False
x+y 43 43 43 43 43

ee expression Heptagon
e\lb e \rb sémantique de ee (une suite)
ln=l\lb l \rb_{n} = l avec ln\lb l \rb_{n} la constante littérale (42,1,False)(42, 1, \text{False}) (nn c’est l’instant nn aka le temps)
e1+e2n=e1n+e2n\lb e_{1} + e_{2} \rb_{n} = \lb e_{1} \rb_{n} + \lb e_{2} \rb_{n}
e1e2n=e1ne2n\lb e_{1} - e_{2} \rb_{n} = \lb e_{1} \rb_{n} - \lb e_{2} \rb_{n}

e1 op e2n=e1n op e2n\lb e_{1} \text{ op } e_{2} \rb_{n} = \lb e_{1} \rb_{n} \text{ op } \lb e_{2} \rb_{n} avec op\text{op} un opérateur quelconque

if e1 then e2 else e3n={e2n si e1n= Truee3n si e1n= False \lb\text{if }e_{1}\text{ then }e_{2}\text{ else }e_{3}\rb_{n} = \begin{cases} \lb e_{2} \rb_{n} & \text{ si } \lb e_{1} \rb_{n} = \text{ True} \\\\ \lb e_{3} \rb_{n} & \text{ si } \lb e_{1} \rb_{n} = \text{ False} \end{cases}

Opérateurs séquentiels

0 1 2
x x0x_0 x1x_1 x2x_2
y y0y_{0} y1y_{1} y2y_{2}
x fby y x0x_{0} y0y_{0} y1y_{1}

Comportement temporel, il y a un état.

Exercice

(* Définir un noeud half dont la sortie o est un
flot booléen qui alterne entre les valeurs
true et false, en commençant par true. *)
node half() returns (o : bool)
let
o = true fby not o;
tel
(* on peut aussi faire *)
node half() returns (o : bool)
let
o = true fby false fby o;
tel

Chronogramme associé :

0 1 2
true fby not o True False True
not o False True False
En Heptagon, = est une équation.
o = true fby not o
= true fby not (true fby not o)
= true fby (not true) fby not (not o)
= true fby false fby o (* c'est notre solution 2 !!*)
= true fby false fby true fby false fby ...

Notes

e1 fby e2 fby e3 = e1 fby (e2 fby e3) ce qui est la conséquence de chaîner plusieurs fby.
\RA associatif à droite

Exercice

(* o1 est comme la fonction half(), o2 est l'inverse en gros *)
node halves() returns (o1, o2 : bool)
let
o1 = true fby o2;
o2 = false fby o1; (* on peut aussi faire o2 = not o1 *)
tel

Exercice

Disgression : positif, c’est >= 0, positivee (anglais) c’est > 0

node j() returns (nat, pos : int)
let
nat = 0 fby pos; (* suite des naturels *)
pos = nat + 1; (* suite des naturels *strictement positifs* *)
tel

En faisant une équation :

nat = 0 fby pos
= 0 fby (nat + 1)
= 0 fby ((0 fby (nat + 1)) + 1)
= 0 fby 1 fby (nat + 2)
= 0 fby 1 fby ((0 fby (nat + 1)) + 2)
= 0 fby 1 fby 2 fby (nat + 3)

Exemple

node j4() returns (x : int)
let
x = 0 -> (pre x + 1);
tel
0 1 2
x = 0 -> pre x + 1 0 1 2
pre x nil 0 1
pre x + 1 nil 1 2

e1e1 fby e2n=e1pre e2n={e10 si n=0pre e2n=e2n1 si n>0 \lb e_{1} e_{1} \text{ fby } e_{2} \rb_{n} = \lb e_{1} \ra \text{pre } e_{2} \rb_{n} = \begin{cases} \lb e_{1} \rb_{0} & \text{ si } n = 0 \\\\ \lb \text{pre } e_{2} \rb_{n} = \lb e_{2} \rb_{n-1} & \text{ si } n > 0 \end{cases}

Analyses statiques

Typage, causalité, initialisation, horloges…

Causalité

```ocaml
node j() returns (nat, pos : int)
let
nat = 0 -> pos;
pos = nat + 1;
tel

Dessin_08-10-2024_13.36.32.excalidraw

Mémo lecture erreur
|| = en parallèle
^qqch = je dois lire qqch
^pos < nat = je dois lire pos avant nat

$ hept -s j -t test.ept
File "test.ept", line 1-5, characters 0-3:
>node j() returns (nat, pos : int)
>let
> nat = 0 -> pos;
> pos = nat + 1;
>tel
Causality error: the following constraint is not causal.
^pos < nat
|| ^nat < pos

Réinitisalisation (modulaire)

Bloc = Soit une liste de blocs, soit une liste d’équations soit la réinitialisation d’un bloc (entre let et tel)
Exemple de bloc de réinitialisation : reset bloc every expression

node nat_reset() returns (o : int)
var c : bool;
let
reset o = 0 fby (o + 1) every c;
c = true fby false fby false fby c;
tel
(* qui est équivalent à *)
node nat_reset2() returns (o : int)
var c : bool;
let
o = if c then 0 else (0 fby (0 + 1));
c = true fby false fby false fby c;
tel
(* exemple avec une fonction externe *)
node nat() returns (o : int)
let
o = 0 fby (o + 1);
tel
node nat_reset() returns (o : int)
var c : bool;
let
(* fonctionne avec une fonctionne *)
reset o = nat() every c;
c = true fby false fby false fby c;
tel
(* ça, ça ne fonctionne pas *)
node nat_reset2() returns (o : int)
var c : bool;
let
o = if c then 0 else nat();
c = true fby false fby false fby c;
tel

Reset = réinitialisation modulaire, passe à travers les abstractions
Utile, mais rend le programme rapidement incompréhensible.

Types structurés et énumérés

Types structurés

type cpl = { re : float; im : float }
node add(x, y: cpl) returns (o: cpl)
let
o = { re = x.re +. y.re
; im = x.im +. y.im };
tel
node conj(x: cpl) returns (o: cpl)
let
o = { re = x.re
; im = -.x.im };
tel

Mot-clé fun permet de définir une node qui ne dépend pas du temps (pas d’état). Donc, par exemple, fby est rejeté des fun.

type cpl = { re : float; im : float }
fun add(x, y: cpl) returns (o: cpl)
let
o = { re = x.re +. y.re
; im = x.im +. y.im };
tel
fun conj(x: cpl) returns (o: cpl)
let
o = { re = x.re
; im = -.x.im };
tel

Types énumérés

automate_enum

l a c a b c b
accept faux vrai faux faux vrai faux faux .. faux .. faux ..
type alpha = A | B | C (* commence par une majuscule, alpha est soit A, B, ou C *)
type etats = X | Y | Z | Mort
node automate(l : alpha) returns (accept : bool)
var etat_courant, etat_precedent : etats;
let
etat_courant = if (etat_precedent, l) = (X, A) then Y
else if (etat_precedent, l) = (Y, B) then Z
else if (etat_precedent, l) = (Y, C) then Y
else if (etat_precedent, l) = (Z, A) then Y
else Mort;
etat_precedent = X fby etat_courant;
accept = (etat_courant = Z);
tel

Horloges

Exercice : Définir une suite o:into : int telle que o2k=ko_{2k} = k o2k+1=0o_{2k+1}=0

o=0,0,1,0,2,0,3,0,4,0,o = 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, \dots

node k() returns (o: int)
var p: bool; v: int;
let
o = if p then v else 0;
v = 0 fby if p then v else (v + 1);
p = true fby not p;
tel

On remarque que oo c’est la suite naturelle séparée de 00.
On peut donc mélanger/entrelacés la suite des entiers naturels et la suite des 0.

Opérateur d’entrelacement merge

node nat() returns (o : int)
let
o = 0 fby (o + 1);
tel
node k() returns (o: int)
var p: bool; v: int;
let
o = merge p v 0;
v = nat();
p = true fby not p;
tel

Contrairement au if qui va perdre des données, le mélange entrelace les flux alors que le if/else choisis quelle valeur garder à un instant tt.

0 1 2 3 4 5
v 0 abs 1 abs 2 abs
p true false true false true false
o 0 0 1 0 2 0
Le merge ne récupère que les informations dont il a besoin pour éviter de faire grossir la mémoire.

Dans notre cas, p est notre horloge, p permet d’alterner le calcul entre p et v.

Sémantique

merge ce1e2{e1ksi ck= true et e1abs,e2k=abse2ksi ck= false et e1=abs,e2kabsabssi ck= abs et e1=e2k=abs \begin{aligned} & \lb \text{merge } c e_{1} e_{2} \rb \\ &\begin{cases} \lb e_{1} \rb_{k} & \text{si } \lb c \rb_{k} = \text{ true et } \lb e_{1} \rb \neq \text{abs}, \lb e_{2} \rb_{k} = \text{abs} \\ \lb e_{2} \rb_{k} & \text{si } \lb c \rb_{k} = \text{ false et } \lb e_{1} \rb = \text{abs}, \lb e_{2} \rb_{k} \neq \text{abs} \\ \text{abs} & \text{si } \lb c \rb_{k} = \text{ abs et } \lb e_{1} \rb = \lb e_{2} \rb_{k} = \text{abs} \end{cases} \end{aligned}

Opérateur de sélection (échantillonnage) e when c

0 1 2 3
nat() 0 1 2 3
nat() when p 0 abs 2 abs

Sémantique

e when c{eksi ck= true et ekabsabssi ck= false et ck=abs \begin{aligned} & \lb e \text{ when } c \rb \\ &\begin{cases} \lb e \rb_{k} & \text{si } \lb c \rb_{k} = \text{ true et } \lb e \rb_{k} \neq \text{abs} \\ \text{abs} & \text{si } \lb c \rb_{k} = \text{ false et } \lb c \rb_{k} = \text{abs} \end{cases} \end{aligned}

ck\lb c \rb_{k} ek\lb e \rb_{k} e when ck\lb e \text{ when } c \rb_{k}
true \neq abs ek\lb e \rb_{k}
false \neq abs abs
abs abs abs

En contraste, voici le tableau pour les additions

e1k\lb e_{1} \rb_{k} e2k\lb e_{2} \rb_{k} e1+e2k\lb e_{1} + e_{2} \rb_{k}
\neq abs \neq abs e1k+e2k\lb e_{1} \rb_{k} + \lb e_{2} \rb_{k}
abs abs abs

Si on a une sortie avec une horloge spéciale, il faut renvoyer l’horloge

node nat() returns (o : int)
let
o = 0 fby (o + 1);
tel
node h() returns (p: bool; o: int)
var x : int;
let
x = nat();
p = true fby not p;
o = x when p;
tel

when false(p) == when not p

Intégrateur

node itgr(y, delta, x0 : float :: .) returns (o : float :: .) (* :: . est inféré par le compilo *)
var area : float;
let
area = delta *. y;
o = (x0 fby o) +. area;
tel
(* là on integre quand meme dans itgr donc on va garder le calcul qu'on veut pas *)
node itgr_enable_bad(y, delta, x0 : float; enable : bool) returns (o : float)
var oi : float;
let
oi = itgr(y, delta, x0) when enable; (*= j'integre tout, tout le temps *)
o = merge enable oi ((x0 fby o) whenot enable); (* maintenant je filtre *)
tel
(* on appelle l'intégrateur uniquement au besoin *)
node itgr_enable(y, delta, x0 : float; enable : bool) returns (o : float)
var oi : float;
let
(* on faite la on décide de notre horloge de base, ici cest when enable *)
oi = itgr(y when enable, delta when enable, x0 when enable);
o = merge enable oi ((x0 fby o) whenot enable); (* grâce au merge on a un o propre *)
tel

Automates

Construction de contrôle.

Grammaire

block = eq
| block; block
| reset block every expression
| automaton ... end

Automate : plusieurs états avec chacun un bloc d’équations, avec un état “éveillé” et les autres “dormants”.
rappel_automate

type lettre = A | B | C
node p(l : lettre) returns (accept : bool)
let
automaton
state X (* état initial pcq c'est le premier *)
do accept = false
unless l = A then Y
state Y
do accept = false
unless l = B then Y (* tester dans l'ordre d'apparition *)
| l = C then Z
state Z
do accept = true
unless l = A then Y
end
tel

Il faut rajouter l’état Dead qui gère quand on sors de l’automate
automate_enum

type lettre = A | B | C
node p(l : lettre) returns (accept : bool)
let
automaton
state X (* état initial pcq c'est le premier *)
do accept = false
unless l = A then Y
| true then Dead
state Y
do accept = false
unless l = B then Y (* tester dans l'ordre d'apparition *)
| l = C then Z
| true then Dead
state Z
do accept = true
unless l = A then Y
| true then Dead
state Dead
do accept = false
end
tel

Transitions fortes/faibles

unless c1c_{1} then s1sns_{1} | \dots | s_{n} then sns_{n}

node a0() returns (o : bool)
let
automaton
state A
do o = true
unless true then B
state B
do o = false
end
tel

until c1c_{1} then s1sns_{1} | \dots | s_{n} then sns_{n}

node a1() returns (o : bool)
let
automaton
state A
do o = true
until true then B
state B
do o = false
end
tel

node a3() returns (o : int)
let
automaton
state A
do o = 1
unless true then B
state B
do o = 2
until true then C
state C
do o = 3
end
tel
(* seul cas avec un *cas transitoire* *)
node a4() returns (o : int)
let
automaton
state A
do o = 1
until true then B
(* skipped *)
state B
do o = 2
unless true then C
state C
do o = 3
end
tel

Transitions réinitialisante/continuante

node f0() returns (o : int)
let
automaton
state X
do o = 0 fby (o + 1)
until (o >= 3) then Y
state Y
do o = 42
until true then X (* a chaque fois qu'on envoie 42, on reviens sur X *)
end
tel

On réinitialise quand on arrive dans l’état.

node f0() returns (o : int)
let
automaton
state X
do o = 0 fby (o + 1)
until (o >= 3) then Y
state Y
do o = 42
unless true then X (* on regarde le unless qui est tjrs true alors le 42 sert à rien *)
end
tel
node f0() returns (o : int)
let
automaton
state X
do o = 0 fby (o + 1)
until (o >= 3) then X (* reinitialisation de l'état X, sans Y *)
end
tel
node f0() returns (o : int)
let
automaton
state X
do o = 0 fby (o + 1)
until (o >= 3) continue X (* on reviens à X sans réinitialiser, donc ça fais rien *)
end
tel
node f0() returns (o : int)
let
automaton
state X
do o = 0 fby (o + 1)
until (o >= 3) continue Y (* quand on va revenir apres Y, on a pas réinitalisé
* alors part direct après l'entier *)
state Y
do o = 42
until true continue X (* a chaque fois qu'on envoie 42, on reviens sur X *)
end
tel

Exemple du bouton

Ce cas illustre que la valeur précédente de o est relative à la définition de l’état, pas au o global.

node switch3bad(b : bool; s : int) returns (o : int)
let
automaton
state Idle
do o = 0 -> (pre o);
unless b continue Increment
state Increment
do o = (0 -> pre o) + s
unless b continue Multiply
state Multiply
do o = (0 -> pre o) * s
unless b continue Idle
end
tel

On peut utiliser le mot clé last

node switch3(b : bool; s : int) returns (last o : int = 0)
let
automaton
state Idle
do (* o = last o; *) (* ne sert à rien car conserve le dernier état *)
unless b continue Increment
state Increment
do o = last o + s
unless b continue Multiply
state Multiply
do o = last o * s
unless b continue Idle
end
tel

block ::= (x1, ..., xn) = expr
| block; block
| automaton ... end (* automaton dans automaton possible ! *)
| reset block every expr
| if expr then block else block
| present ... end
| switch ... end

Exemple automaton dans automaton

node f(cmd : bool) returns (o : float)
let
automaton
state Init (* état d'initialisation *)
do o = 0.0
unless cmd then Cruise
state Cruise (* état de croisière *)
var y : float;
do automaton
state A
do y = 0.0 fby (y +. 1.0)
until y >=. 10.0 then B
state B
do y = 1.0 fby (2.0 *. y)
until y >=. 128.0 then A
end;
o = y +. 10.0;
end
tel

switch et present

type color = Green | Amber | Cyan
fun color2rgb(c : color) returns (r, g, b : float)
let
switch c
| Green
do r = 0.0; g = 1.0; b = 0.0
| Amber
do r = 1.0; g = 0.75; b = 0.0
| Cyan
do r = 0.0; g = 1.0; b = 1.0
end
tel
fun sign(x : float) returns (o : float)
let
present (* le booléen doit être combinatoire (sans état) *)
| x >. 0.0 do o = 1.0
| x <. 0.0 do o = -. 1.0
default do o = 0.0
end
tel

Les tableaux

node arr0() returns (o1, o2, o3 : int^3)
let
o1 = [0, 1, 2];
o2 = 42^3;
o3 = o1 fby o2;
tel
node arr1() returns (o : int^2)
var n : int;
let
n = 0 fby (n + 2);
o = [n, n + 1];
tel
expr[expr]
----
|> expression statique (* permet au compilo de rester dans les bornes du tableau *)
expr[> expr <]
----
|> expression (* quelconque, pas besoin que ça soit statique *)
expr.[expr] default expr
----
|> expression
[ expr with [ expr ] = expr ]
---- ---- ---- ----
| | | |> : t
| | |> : int
| |> mot clé
|> : t^n

Exemple registre à décalage

(* les constructions dynamique sont à chier parce que si on a un bug il faudra débogguer *)
(* 3 bits + 1 entier de registre utilisé *)
node shiftr3dyn(ini : bool^3) returns (o : bool)
var mem : bool^3; idx : int;
let
mem = ini fby mem;
idx = 0 fby ((idx + 1) % 3);
o = mem[> idx <];
tel
node shiftr3dyn_bis(ini : bool^3) returns (o : bool)
var mem : bool^3; idx : int;
let
mem = ini fby mem;
idx = 0 fby ((idx + 1) % 3);
o = mem.[idx] default false;
tel
(* 3 bits de registre *)
node shiftr3(ini : bool^3) returns (o : bool)
var mem : bool^3;
let
mem = ini fby [ mem[1], mem[2], mem[0] ];
o = mem[0];
tel

\RA On ne peut toujours pas itérer dans un tableau.

(* pas idéal parce que s'il y a un bug dans le code, il faudra le débogger *)
node arr2() returns(o1, o2 : float^3)
let
o1 = [1.0, 3.0, 10.0]
o2 = [ o1 with [ 1 ] = 42.5 ]; (* modifie à l'index 1 *)
tel
node arr3() returns(o1, o2 : float^3)
let
o1 = [1.0, 3.0, 10.0]
o2 = [ o1 with [ 37 ] = 42.5 ]; (* modification ignorée car en dehors des bornes *)
tel
(* concaténatio de tableau *)
expr @ expr
node concat(a : int^1; b : int^2) returns (o : int^3)
let
o = a @ b;
tel

Buffer circulaire

node ring_buffer(e : int; w, r : bool) returns (o : int)
var pa, a : int^7; (* pa : précedent, a : courant*)
w_idx, r_idx : int; (* indices écriture et lecture *)
let
pa = (0^7) fby a;
a = if w then [ pa with [ w_idx ] = e ] else pa;
o = a[> r_idx <];
w_idx = 0 fby (w_idx + (if w then 1 else 0) % 7);
r_idx = 0 fby (r_idx + (if r then 1 else 0) % 7);
tel
(* on peut également déclarer une constante *)
const n : int = 7
node ring_buffer(e : int; w, r : bool) returns (o : int)
var pa, a : int^n; (* pa : précedent, a : courant*)
w_idx, r_idx : int; (* indices écriture et lecture *)
let
pa = (0^n) fby a;
a = if w then [ pa with [ w_idx ] = e ] else pa;
o = a[> r_idx <];
w_idx = 0 fby (w_idx + (if w then 1 else 0) % n);
r_idx = 0 fby (r_idx + (if r then 1 else 0) % n);
tel
(* on peut également donner en paramètre une constante *)
node ring_buffer<<n : int>>(e : int; w, r : bool) returns (o : int)
var pa, a : int^n; (* pa : précedent, a : courant*)
w_idx, r_idx : int; (* indices écriture et lecture *)
let
pa = (0^n) fby a;
a = if w then [ pa with [ w_idx ] = e ] else pa;
o = a[> r_idx <];
w_idx = 0 fby (w_idx + (if w then 1 else 0) % n);
r_idx = 0 fby (r_idx + (if r then 1 else 0) % n);
tel
node test_buffer() returns (o : int)
var e : int; w, r : bool;
let
e = 0 fby (e + 1);
w = true fby false fby w;
r = false fby true fby r;
o = ring_buffer<<10>>(e, w, r);
tel

buffer_circ

Exercice/Problème : e devrait être d’horloge w et o devrait être d’horloge r. On pourrait aussi ajouter une sortie d’erreur qui détecte si le producteur ou le consommateur est trop rapide.

Itérateurs

map / fold / mapfold

Ainsi que mapi, foldi et mapfoldi

dessin

fun add1(a : int) returns (b : int)
let
b = a + 1;
tel
const n : int = 4
node main (x : int^n) returns (y : int^n)
let
(* On peut enchainée les maps *)
y = map<<n>> add1(map<<n>> add1(x));
tel

Type

map

types

fun add1(a : int) returns (b : int)
let
b = a + 1;
tel
const n : int = 4
node main (x : int^n) returns (y : int^n)
let
(* On peut enchainée les maps *)
y = map<<n>> add1(map<<n>> add1(x));
tel

(y1,y2)=map<<n>> F(x1,x2,x3)1in,(y1[i],y2[i])=F(x1[i],F(x2[i],F(x3[i]) \begin{aligned} (y^{1},y^{2}) = \text{map<<}n\text{>> } F(x^{1},x^{2},x^{3}) \\\\ \forall 1 \leq i \leq n, (y^{1}[i], y^{2}[i]) = F(x^{1}[i], F(x^{2}[i], F(x^{3}[i]) \end{aligned}

fold

fold
y = fold<<n>> F(x, a);
Par exemple : y = F(x[2], F(x[1], F(x[0], a)))

fun add(a, b : int) returns (c : int)
let
c = a + b;
tel
fun sum<<n : int>>(x : int^n) returns (s : int)
let
s = fold<<n>> add(x, 0);
tel
node main() returns (s : int)
let
s = sum<<3>>([1, 2, 3]); (* renvois 6 *)
tel

types_fold

mapfold

mapfold
(z, y) = mapfold<<n>> F(x, v);

fun f(a, acc : int) returns (b, newacc : int)
let
b = a + acc;
newacc = b; (* cas simple *)
tel
node csum_array<<n : int>>(x : int^n) returns (z : int^n)
var y : int;
let
(z, y) = mapfold<<n>> f(x, 0 fby y);
tel
node main() returns (s : int^3)
let
s = csum_array<<3>>([1, 2, 3]);
tel

mapfold-circuit

types_mapfold

fun add2(a, b : int) returns (c : int)
let
c = a + b;
tel
fun test(x : int^3) returns (o : int^3)
var y : int^3;
let
y = [ x with [1] = 42 ];
o = map<<3>> add2(x, y);
tel

Exercices

1. Le son

son