Previous Up Next

3  Stratégies de réduction et langages de programmation

On a vu qu’un λ-terme pouvait contenir plusieurs rédex. Si l’on vu calculer la forme normale d’un terme u (si elle existe), on va pouvoir l’obtenir en choisissant un rédex dans u, en le contractant, et en répétant le processus jusqu’à ce qu’il n’y ait plus de rédex. Ce choix du rédex à chaque étape de réduction est appelé une stratégie de réduction.

3.1  Stratégies internes

Prenons le cas d’un langage de programmation usuel, comme OCaml ou Pascal ou C. Dans ces langages, un terme de la forme uv (noté u(v) en Pascal ou en C) s’évalue typiquement en calculant d’abord la valeur f de u, qui doit être une fonction, puis en calculant la valeur a de v, puis en appliquant f à a. (En général, u est un identificateur qui dénote déjà une fonction, sans qu’on ait à calculer quelle fonction c’est exactement.) Remis dans un cadre λ-calculatoire, on peut dire qu’on a d’abord réduit les rédex de u, puis quand il n’y en a plus eu, on a réduit les rédex de v, enfin on regarde ce qu’est devenu u, et s’il est devenu un terme de la forme λ x · t, on applique cette dernière fonction à v pour donner t[x:=v], et on continue.

Ce genre de stratégie est appelée une stratégie interne (innermost en anglais), parce qu’elle réduit d’abord les rédex les plus à l’intérieur des termes d’abord. La stratégie particulière présentée préfère de plus les rédex de gauche à ceux de droite: elle ne réduit les rédex de droite que quand il n’y en a plus à gauche. Il s’agit d’une stratégie interne gauche. Une stratégie interne droite est évidemment envisageable, et se rapprocherait de la sémantique de OCaml. Un petit test qui le montre est de taper:

let a = ref 0;;
let f x y = ();;
f (a:=1) (a:=2);;
a;;

qui crée une référence (une cellule de valeur modifiable par effet de bord), puis une fonction bidon f qui ignore ses deux arguments. Ensuite on calcule f appliquée à deux arguments qui affectent 1, resp. 2 à a. Le langage étant séquentiel, la valeur finale de a permettra de savoir lequel des arguments a été évalué en dernier. En OCaml, la réponse est 1, montrant que c’est l’argument de gauche qui a été évalué en dernier.

Évidemment, en λ-calcul on n’a pas d’affectations, et le fait qu’une stratégie interne soit gauche ou droite ne change rien au résultat final.

3.2  Stratégies externes

On peut aussi décider d’utiliser une stratégie externe, qui réduit les rédex les plus externes d’abord. Autrement dit, alors qu’une stratégie interne réduit (λ x · u) v en normalisant λ x· u vers λ x · u′, en normalisant v vers v′, puis en réduisant u′ [x:=v′], une stratégie externe commence par contracter (λ x· u) v en u [x:=v], qu’il réduit à son tour.

Vu au travers du filtre des langages de programmation usuels, voici ce que ces stratégies donnent pour l’évaluation de fact (1+2), en supposant la définition de fact donnée au début de ce chapitre, et les règles de réduction typiques de +, ×, etc. Pour se fixer les idées, on utilisera celles de ⌈fact⌉, ⌈+⌉, ⌈×⌉.

On commence par regarder la stratégie externe gauche. Comme ⌈fact⌉ = Yf · λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (f (P n)))), par une stratégie externe on aura ⌈fact⌉ →* λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (⌈fact⌉ (P n)))). On obtient alors:

  
 fact⌉ (⌈+⌉   ⌈1⌉   ⌈2⌉) 
    →*(λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (⌈fact⌉ (P n)))) (⌈+⌉   ⌈1⌉   ⌈2⌉) 
    →if⌉ (⌈zero?⌉ (⌈+⌉   ⌈1⌉   ⌈2⌉))   ⌈1⌉  (⌈×⌉ (⌈+⌉   ⌈1⌉   ⌈2⌉)  (⌈fact⌉ (P (⌈+⌉   ⌈1⌉   ⌈2⌉))))

mais ici on voit que l’argument (⌈+⌉   ⌈1⌉   ⌈2⌉) a été copié en trois endroits. Chaque occurrence devra être réduite en ⌈3⌉ indépendamment des autres; autrement dit, on va devoir calculer 1+2 = 3 trois fois !

Pour voir ça plus clairement, reprenons une notation plus proche de la notation OCaml, et en particulier plus agréable. Nous réécrivons la réduction ci-dessus et la complétons:

  
 fact (1+2) 
    →*(λ n · if n=0 then 1 else n × fact (n−1)) (1+2) 
    →if (1+2)=0 then 1 else (1+2) × fact ((1+2)−1) 
 …où l’on passe quelques temps à réduire (1+2)=0 en F … 
    →*if F then 1 else (1+2) × fact ((1+2)−1) 
    →(1+2) × fact ((1+2)−1) 
    →*…où 1+2 n’est toujours pas évalué à 3 … 
 …on réduit comme ça jusqu’à la fin de la récursion … 
    →*(1+2) × (((1+2)−1) × ((((1+2)−1)−1) × 1)) 
    →*…et après un temps certain, on obtient le résultat final, 
    →*6

3.3  Stratégies internes faibles, appel par valeur

Maintenant, examinons ce qui se passe avec une stratégie interne. Nous allons commencer par supposer que la factorielle ⌈fact⌉ s’évalue par la stratégie interne en λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (⌈fact⌉ (P n)), et que ceci est normal. (Il y a un problème avec cette hypothèse, que nous résoudrons par la suite.) Alors le même calcul, par la stratégie interne, est beaucoup plus direct. Dans un souci de lisibilité, nous l’écrivons en notation pseudo-OCaml:

  
 fact (1+2) 
    →*(λ n · if n=0 then 1 else n × fact (n−1)) (1+2) 
    →*(λ n · if n=0 then 1 else n × fact (n−1)) (3) 
    →if 3=0 then 1 else 3 × fact (3−1)) 
    →*if F then 1 else 3 × fact (3−1)) 
    →3 × fact (3−1) 
    →*3 × (λ n · if n=0 then 1 else n × fact (n−1)) (2) 
    →*…par le même procédé … 
 3 × (2 × fact (2−1)) 
    →*3 × (2 × (1 × 1)) 
    →*6

ce qui correspond beaucoup plus à ce à quoi on s’attend de la part d’un langage de programmation.

Cependant, dans l’exemple ci-dessus, nous avons triché deux fois.

Première tricherie: Nous avons supposé que ⌈fact⌉ s’évaluait par la stratégie interne en λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (⌈fact⌉ (P n)), et que ceci était normal. Mais c’est faux ! En fait, ⌈fact⌉ est de la forme Y F, pour un certain F, et on a:

    Y F(λ x · F (xx)) (λ x · F (xx)) 
 F ((λ x · F (xx)) (λ x · F (xx))) 
 F ( F ((λ x · F (xx)) (λ x · F (xx)))) → …

qui ne termine jamais !

Le remède est que dans les langages de programmation usuels, on ne réduit pas sous les λ. Autrement dit, on considère que λ x · u est déjà suffisamment évalué. De telles stratégies s’appellent des stratégies de réduction faible. Elles ne sont évidemment pas suffisantes pour obtenir une forme normale — par exemple, aucune règle de réduction faible ne s’applique pour réduire λ x · (λ y · y) x. Mais ça n’est pas très important dans un langage de programmation; en un sens, par programme on veut calculer des entiers, des listes, des structures de données en somme. Si un programme retourne une fonction, c’est qu’il attend encore au moins un argument pour pouvoir continuer le calcul.

La stratégie de réduction interne faible peut maintenant se formaliser par les règles suivantes, où uv signifie “u se réduit en une étape en v” par cette stratégie:

L’argument V de l’abstraction dans la règle (β▷) est contraint à être une valeur. On définit ici les valeurs V par récurrence sur la structure de V comme étant les variables, les applications V1 V2 d’une valeur V1 qui n’est pas une abstraction à une valeur V2, et les abstractions quelconques λ x · u. De façon équivalente:

  V ::=  V V … V ∣ λ x · Λ

Ces règles sont une façon commode de dire que ▷ est la plus petite relation binaire telle que (λ x· u) Vu [x:=V] pour tout terme u et toute valeur V, et passant aux contextes applicatifs (règles (App1▷) et (App2▷)), mais ne passant pas nécessairement aux contextes d’abstraction. Formellement, les formules (ici, de la forme uv) au-dessus des barres sont des prémisses, qu’il faut vérifier avant de pouvoir appliquer la règle et d’en déduire la conclusion qui est en-dessous de la barre.

Pour corriger le problème de non-terminaison de la définition de ⌈fact⌉ en tant que point fixe, on va définir ⌈fact⌉ =def Yv [F] au lieu de Y F, où F est la fonctionnelle définissant ⌈fact⌉, soit:

  F =def λ f · λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (f (P n)))

et Yv [F] est définie de sorte d’une part que Yv [F] V* F (Yv [F]) V pour toute valeur V, et d’autre part que Yv [F] soit une valeur. On peut y arriver en effectuant quelques η-expansions dans Y F, comme le montre l’exercice 21. Selon la stratégie interne faible, on aura alors:

  
    ⌈fact⌉ =Yv [F
    ▷*(λ f · λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (f (P n)))) (Yv [F]) 
    =(λ f · λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (f (P n)))) ⌈fact⌉ 
    ▷λ n · ⌈if⌉ (⌈zero?⌉ n)   ⌈1⌉  (⌈×⌉ n  (⌈fact⌉ (P n))

où le dernier terme est une valeur, et donc la définition de ⌈fact⌉ ne boucle plus.

Exercice 21   Définissons Yv [F] =def λ y · (λ x · Fz · xxz)) (λ x · Fz · xxz)) y. Montrer que Yv [F] ▷* F (Yv [F]) V pour toute valeur V.

Deuxième tricherie: Le calcul que l’on a fait de la factorielle de 3 par stratégie interne, maintenant modifiée en une stratégie interne faible, n’est toujours pas correct, même si on remplace → par ▷. En effet, la réduction:

  
 if F then 1 else 3 × fact (3−1) 
    ▷3 × fact (3−1)

est incorrecte: ce n’est pas une réduction par stratégie interne. En effet, revenons à une écriture plus formelle, alors la ligne du dessus est F ⌈1⌉ (⌈×⌉  ⌈3⌉   (⌈fact⌉ (P ⌈3⌉))), mais la réduction par stratégie interne demande que l’on calcule d’abord la valeur ⌈6⌉ de ⌈×⌉  ⌈3⌉   (⌈fact⌉ (P ⌈3⌉)) avant de conclure que l’on peut normaliser F ⌈1⌉ ⌈6⌉ en ⌈6⌉. Or ce que nous avons fait ici, c’est utiliser une étape de stratégie externe, pour éviter de garder l’argument ⌈1⌉ en attente, puisque son destin est d’être éliminé de toute façon.

Une conclusion provisoire est que les langages de programmation utilisent en fait une stratégie interne faible pour les appels de fonction normaux, mais une stratégie externe (faible elle aussi) pour les tests. La raison est pragmatique: il est plus avantageux de calculer de façon interne les appels de fonctions normaux, parce que leurs arguments devront en général être utilisés par la fonction, et que s’ils sont utilisés plusieurs fois, on n’aura pas à recalculer leur valeur plusieurs fois; mais d’autre part, les arguments en position “then” et “else” d’un if ne sont jamais utilisés plus d’une fois, et en fait l’un des deux ne sera pas utilisé, il est donc plus intéressant d’utiliser une stratégie externe dans ce cas, ce qui économise l’évaluation de l’argument qui ne sera jamais évalué.

3.4  Standardisation, appel par nom

Il est bon de remarquer, cependant, que changer de stratégie n’est pas innocent: certains termes ont une réduction interne qui termine mais aucune réduction externe finie. (Prendre l’exemple de l’exercice 6.) En revanche, et aussi bizarre que ça paraisse, la réduction externe gauche, aussi inefficace qu’elle puisse être en pratique, est optimale dans le sens suivant:

Théorème 3 (Standardisation)   Si u est faiblement normalisant, alors la stratégie externe gauche calcule la forme normale de u par une réduction finie.

Autrement dit, la stratégie externe est peut-être lente, mais elle est sûre: elle ne se laisse pas embarquer dans des bouclages inutiles.

Preuve :

Avant de commencer, faisons quelques remarques sur les stratégies externes:

Nous donnons maintenant une preuve du théorème due à René David. On peut définir de façon astucieuse la notion de réduction standards de u vers v, comme étant la relation binaire définie par récurrence sur la taille de v, par us v si et seulement si:

  1. v s’écrit sous forme de tête λ x1, …, xn · v0 v1vm,
  2. u se réduit par réduction de tête en une forme de tête λ x1, …, xn · u0 u1um,
  3. si v0 est une variable x, alors u0=x, et si v0 est une abstraction λ x · v′ alors u0 est de la forme λ x · u′ et u′ ⇒s v′;
  4. u1s v1, …, ums vm.

Il est clair que la stratégie externe gauche est une stratégie standard. En effet, la stratégie externe gauche commence par calculer la forme normale de tête (par →t*) λ x1, …, xn · u0 u1um, puis normalise u1 par la stratégie externe gauche, puis u2, …, enfin um; u0 étant une variable, sa normalisation est immédiate. La notion de réduction standard est plus souple en ce sens qu’on ne demande pas que u0 soit une variable, et que l’on peut alterner des réductions dans u1 avec des réductions dans u2, …, um, et même dans u0.

On va démontrer que: (*) si u* v, alors us v, pour tous termes u, v. Ceci impliquera le théorème: si u a une forme normale v, alors us v par (*), et il ne reste plus qu’à démontrer par récurrence sur la taille de v que cette réduction standard induit une réduction externe gauche de u vers v. Si v est une variable, alors cette réduction standard est une réduction de tête, qui est bien une réduction externe gauche. Sinon, écrivons v sous la forme λ x1, …, xn · v0 v1vm. Alors nécessairement la réduction standard de u vers v commence par effectuer une réduction de tête de u vers une forme de tête λ x1, …, xn · u0 u1um suivie de réductions standard u0s v0, u1s v1, …, ums vm. Ces réductions standard induisent des réductions externes gauches par hypothèse de récurrence, donc la réduction:

    
      u →t*λ x1, …, xn · u0 u1 u2 … um 
      →*λ x1, …, xn · v0 u1 u2 … um 
      →*λ x1, …, xn · v0 v1 u2 … um 
      →*λ x1, …, xn · v0 v1 v2 … um 
      →*… 
      →*λ x1, …, xn · v0 v1 v2 … vmv

est une réduction externe gauche.

Montrons maintenant (*). En première lecture, il est conseillé de sauter la démonstration, qui est assez technique. L’idée est de permuter les applications de la règle (β), et la difficulté est de le faire dans le bon ordre.

() On va démontrer (*) par étapes. Le point important est le numéro 10 ci-dessous, les précédents sont des points techniques:

  1. D’abord, on remarque que si u=v, alors us v; autrement dit, ⇒s est réflexive. Ceci se démontre par une récurrence facile sur la taille de v, où les réductions de tête impliquées sont toutes de longueur nulle;
  2. Ensuite, si ut* v, alors us v. Écrivons v sous forme de tête λ x1, …, xn · v0 v1vm. Par hypothèse, ut* λ x1, …, xn · u0 u1umui = vi pour tout i. En particulier, par le point 1, uis vi pour tout i ≥ 1. De plus, si u0=v0 est une abstraction λ x · u′, alors u′ ⇒s u′ par le point 1.
  3. Si ut* v et vs w, alors us w: analyse de cas facile.
  4. Si λ x · u′ ⇒s v, alors v est de la forme λ x · v′ et u′ ⇒s v′. Pour ceci, on applique la définition de nouveau, en observant d’abord que si λ x · u′ →t w, alors w s’écrit λ x · w′ avec u′ →t w′.
  5. Si u se réduit par réduction de tête en une forme de tête λ x1, …, xn · u0 u1umn=0, alors il s’y réduit par une réduction de tête faible, c’est-à-dire opérant sous un nombre nul de λ (ceci permet de réécrire λ x1, …, xn · (λ x · t) s1sm → λ x1, …, xn · t [x:=s1] s2sm uniquement si n=0). En effet, tout réduction de tête qui n’est pas faible ne peut se réduire qu’en des termes qui ont au moins un λ en tête.
  6. Si us v et u′ ⇒s v′, alors uu′ ⇒s vv′. Par définition, v s’écrit sous forme de tête λ x1, …, xn · v0 v1vm, u se réduit par réduction de tête en une forme de tête λ x1, …, xn · u0 u1um, de sorte que les conditions 3 et 4 de la définition de ⇒s soient vérifiées.

    Si n=0, par le point 5, u se réécrit par réduction de tête faible en u0 u1um. L’intérêt de la réduction de tête faible est qu’alors uu′ se réduit par réduction de tête (faible de nouveau) en u0 u1um u′. Ce genre de propriété de stabilité n’est pas vraie pour les réductions de tête générales. On en conclut immédiatement uu′ ⇒s vv′.

    Si n≠ 0, considérons le plus grand préfixe de la réduction de tête de u à λ x1, …, xn · u0 u1um qui forme une réduction de tête faible. On a donc un terme t, et une suite de réductions de tête ut* tt* λ x1, …, xn · u0 u1um où les réductions de tête de u à t sont faibles, et les suivantes non. Comme n ≠ 0, on note que t s’écrit lui-même comme une abstraction λ x1 · t′, soit parce que t = λ x1, …, xn · u0 u1um, soit parce qu’il y a au moins une étape de réduction de tête de t vers λ x1, …, xn · u0 u1um, et qu’elle n’est pas faible. On note alors que t′ →t* λ x2, …, xn · u0 u1um.

    On en déduit que uu′ →t*x1 · t′) u′ (par la propriété de stabilité des réductions de tête faibles remarquée plus haut), t′ ⇒s λ x2, …, xn · v0 v1vm (en utilisant le point 3), u′ ⇒s v′, donc uu′ ⇒sx1 · (λ x2, …, xn · v0 v1vm)) v′ = vv′ par définition.

  7. Si us v alors λ x · us λ x · v. C’est une récurrence immédiate sur v, en notant que si ut* u′ implique λ x · ut* λ x · u′ (propriété vraie de la réduction de tête, mais pas de la réduction de tête faible cette fois-ci!).
  8. Si u0s w0, u1s w1, …, ums wm, alors λ x1, …, xn · u0 u1ums λ x1, …, xn · w0 w1wm. Ceci peut sembler être juste une reformulation de la définition, mais la définition requiert que λ x1, …, xn · u0 u1um et λ x1, …, xn · w0 w1wm soient des formes de tête, ce que l’on ne demande pas ici. La propriété se démontre par application itérée des points 6, puis 7.
  9. Si u′ ⇒s w′ et u1s w1, alors u′ [x:=u1] ⇒s w′ [x:=w1]. Ceci se démontre par récurrence sur la taille de w′. Écrivons w′ sous forme de tête λ y1, …, yn · w0 w1wm. Par hypothèse u′ →t* λ y1, …, yn · u0 u1um, où u1s w1, …, ums wm, et soit u0 et w0 sont des variables identiques, soit u0 = λ x · s, w0 = λ x · t et ss t. Par hypothèse de récurrence, u1 [x:=u1] ⇒s w1 [x:=w1], …, um [x:=u1] ⇒s wm [x:=w1]. De plus, u0 [x:=u1] ⇒s w0 [x:=w1], par l’argument suivant: si u0=w0=x, ceci provient de u1s w1, si u0 et w0 sont égaux à la même variable y et yx, alors ys y par le point 1, et si u0 = λ x · s, w0 = λ x · t et ss t, alors ceci vient de s [x:=u1] ⇒s t [x:=w1] (hypothèse de récurrence) et du point 7.

    Comme ui [x:=u1] ⇒s wi [x:=w1] pour tout i, 0≤ im, le point 8 entraîne que (λ y1, …, yn · u0 u1um) [x:=u1] = λ y1, …, yn · u0 [x:=u1] u1 [x:=u1] … um [x:=u1] ⇒s λ y1, …, yn · w0 [x:=w1] w1 [x:=w1] … wm [x:=w1] = w′ [x:=w1]. Puisque u′ →t* λ y1, …, yn · u0 u1um, on a aussi u′ [x:=u1] →t*y1, …, yn · u0 u1um) [x:=u1], et le point 3 implique donc u′ [x:=u1] ⇒s w′ [x:=w1].

  10. Si us wv, alors us v. La démonstration est par récurrence sur la taille de w.

    Par définition, u se réduit par réduction de tête en une forme de tête λ x1, …, xn · u0 u1 u2um, w a la forme de tête λ x1, …, xn · w0 w1 w2wm et uis wi pour tout i ≥ 1, et soit u0 et w0 sont la même variable, soit u0 = λ x · u′, w0 = λ x · w′ pour certains terms u′ et w′ tels que u′ ⇒s w′.

    La réduction de w vers v peut maintenant se produire en deux types d’endroits.

    Cas 1: elle se produit dans l’un des wj (et si j=0, dans w′). Alors v = λ x1, …, xn · v0 v1 v2vn, avec vi = wi pour tout ij, et wjvj sinon. Noter que ujs wjvj, et que la taille de wj est plus petite que celle de w, donc on peut appliquer l’hypothèse de récurrence et conclure que ujs vj. D’autre part, pour ij on a uis wi = vi. Donc dans tous les cas, pour tout i, uis vi. Il s’ensuit que us v, en utilisant le point 8.

    Cas 2: la réduction de w vers v est une réduction de tête. C’est l’unique cas intéressant de la démonstration. Noter qu’alors on a nécessairement u0 = λ x · u′, w0 = λ x · w′ pour certains terms u′ et w′ tels que u′ ⇒s w′. De plus, v = λ x1, …, xn · w′ [x:=w1] w2wm.

    L’idée est qu’au lieu de contracter le rédex (λ x · w′) w1 dans w (donc tard dans la réduction) on va contracter le rédex qui lui fait pendant beaucoup plus tôt dans la réduction, (λ x · u′) u1. Formellement, on étend la réduction de tête partant de u en:

          
            u →t*        λ x1, …, xn · (λ x· u′) u1 u2 … um 
            →tλ x1, …, xn · u′ [x:=u1u2 … um.

    Puis l’on note que u′ [x:=u1] ⇒s w′ [x:=w1] par le point 9, uis wi pour tout i ≥ 2 par hypothèse, donc λ x1, …, xn · u′ [x:=u1] u2ums λ x1, …, xn · w′ [x:=w1] w2wm = v par le point 8.

Nous démontrons maintenant (*), à savoir que u* v implique us v, par récurrence sur la longueur k de la réduction donnée de u vers v. Si cette longueur est 0, alors u=v et on conclut par le point 1. Sinon, c’est que cette réduction s’écrit u* wv, où la réduction de u à w est de longueur k−1. Par hypothèse de récurrence, us w, et par le point 4 ci-dessus, us v.     ♦

La stratégie externe gauche (leftmost outermost strategy en anglais) est souvent appelée réduction gauche, ou stratégie d’appel par nom. Cette dernière dénomination vient du langage Algol, et caractérise le fait que dans (λ x · u) v, on remplace d’abord x par v dans u avant de réduire v — le terme non réduit v étant pris comme nom de sa valeur.

Exercice 22   Montrer que si u* x, où x est une variable, alors il existe une réduction de tête de u à x. (Regarder la preuve du théorème 3.)
Exercice 23 ()   Utiliser la notion de réduction standard, et en particulier le point (*) de la démonstration du théorème 3 pour montrer directement que le λ-calcul est confluent.

D’un point de vue pratique, sachant que ni les stratégies externes (appel par valeur) ni les stratégies internes (appel par nom) ne sont des panacées, pourquoi ne pas utiliser d’autres stratégies ? Une qui semble intéressante est la stratégie d’appel par nécessité, utilisée dans les langages comme Miranda ou Haskell. L’idée, c’est de partager tous les sous-termes d’un λ-terme, et de considérer les λ-termes comme des graphes. On verra comment faire cela dans la troisième partie de ce cours, intitulée “Machines, Interprètes”. Disons tout de suite que ce n’est pas, en fait, une panacée.


Previous Up Next