Previous Up Next

5  Style de passage de continuations

Les modèles sémantiques dénotationnels que l’on a vus jusqu’ici — dits en style direct — ont l’avantage d’oublier tous les détails liés à l’ordre d’évaluation, excepté les points importants comme la terminaison (valoir ⊥ ou pas). C’est parfois aussi un inconvénient, et on aimerait parfois pouvoir exprimer une partie de la stratégie d’évaluation. Par exemple, dans la sémantique proposée en figure 4, rien ne dit que pour évaluer uv, on doive évaluer u avant v.

C’est gênant notamment si l’on enrichit notre λ-calcul avec des constructions telles que les affectations (setq en Lisp, := ou <- en Caml, := en Pascal, = en C), ou les sauts non locaux (try et raise en Caml, goto en Pascal, setjmp et longjmp en C). Prenons le cas des affectations tout d’abord, parce qu’il est plus simple à expliquer. Supposons qu’on veuille calculer la valeur de uv, et que l’évaluation de u affecte une référence x à 1, et l’évaluation de v affecte x à 2. Si u est évalué avant v, alors x vaudra 2 à la fin du calcul, et si c’est v, alors x vaudra 1 à la fin du calcul: la sémantique des deux cas n’est donc pas la même.

5.1  Le concept de continuation

Une façon de spécifier l’ordre d’évaluation en sémantique dénotationnelle est de faire prendre à la fonction d’évaluation un deuxième argument, la continuation κ, qui est une fonction de D vers D, et de convenir que:

  [u] ρ κ

est la valeur non pas de u mais d’un programme tout entier dont un sous-terme est u: κ dénote la suite du calcul une fois qu’on aura évalué u, et si la valeur de u est d, alors la valeur du programme tout entier sera κ (d). Graphiquement, on peut imaginer que l’on a un programme π, dont l’exécution va consister en une suite d’instructions, et qu’au milieu de cette suite on trouve une sous-suite d’instructions pour évaluer u:

Lorsque l’exécution se trouve au début de u (au point 1), ce graphique dit que ce qu’il reste à faire pour trouver la valeur de π au point 3, c’est calculer la valeur de u, et une fois qu’on l’aura (au point 2), il suffira d’appliquer κ, la suite du calcul.

Formellement, examinons déjà comme on peut modéliser une stratégie (faible) d’appel par valeur gauche sur les λ-termes purs par ce moyen (le codage des booléens, des entiers, etc., est laissé en exercice), et nous verrons ensuite comment ce style de sémantique permet de modéliser les constructions de style setjmp/longjmp, puis les affectations.

Tout d’abord, [x] ρ κ, lorsque x est une variable, doit évaluer x, et passer sa valeur ρ (x) à κ. On va donc définir [x] ρ κ =def κ (ρ (x)).

Dans le cas des applications, [uv] ρ κ doit d’abord évaluer u, puisque nous avons décidé d’une stratégie gauche. [uv] ρ κ vaudra donc [u] ρ κ′ pour une continuation κ′ à trouver. Cette continuation κ′ est une fonction qui prend la valeur f de u, et va ensuite calculer v. Donc κ′ doit être de la forme (f[v] ρ′ κ″) pour un environnement ρ′ et une continuation κ″ à trouver. Mais v s’évalue dans le même environnement que uv, donc ρ′=ρ. D’autre part, κ″ doit être une fonction qui prend la valeur d de v, applique la valeur f de u (que l’on supposera être une fonction) à d, et continue le reste du calcul (κ) avec la valeur f (d). En résumé:

  [uv] ρ κ =def [u] ρ (f ↦ [v] ρ (d ↦ κ (f (d))))

Cette formule peut avoir l’air illisible, mais voici un truc pour déchiffrer ce genre de notations intuitivement: [u] ρ (f ↦ … se lit “évaluer u dans l’environnement ρ, ceci donne une valeur f; ensuite …”. D’autre part, appliquer κ à une valeur d se lit: “retourner d”. La formule ci-dessus se lit donc:

Évaluer u dans l’environnement ρ, ceci donne une valeur f; ensuite,
évaluer v dans l’environnement ρ, ceci donne une valeur d; ensuite,
retourner f (d).

Pour évaluer une λ-abstraction λ x · u dans l’environnement ρ et la continuation κ, on va d’abord calculer la valeur de λ x · u, et ensuite la retourner par κ. Cette valeur est une fonction qui prend une valeur d pour x, et ensuite évalue u …mais avec quelle continuation ? L’idée, c’est qu’une fois que le calcul de u sera terminé, la continuation devra juste retourner cette valeur: la continuation cherchée est l’identité id. En somme:

  [λ x· u] ρ κ =def κ (d ↦ [u] (ρ[x:=d]) id)

Cette sémantique, qui est notre premier exemple d’une sémantique par passage de continuations, est résumée en figure 5. Nous avons affiné la définition dans le cas de l’application uv, pour tenir compte de ce qui se passe lorsque f n’est pas une fonction dans le domaine sémantique D considéré. Noter qu’on ne retourne pas ⊥ (on n’a pas écrit κ (⊥)), mais on interrompt l’exécution complète du programme, qui retourne immédiatement ⊥. (C’est un plantage.)


      [x] ρ κ=defκ (ρ (x)) 
      [uv] ρ κ=def
      [u] ρ 

f ↦ [v] ρ 

d ↦

          κ (f (d))si f est une fonction 
          ⊥sinon




      [λ x· u] ρ κ=defκ (d ↦ [u] (ρ[x:=d]) id)
Figure 5: Une sémantique de l’appel par valeur en style de passage de continuations

On notera que l’on aurait pu définir l’application autrement, par exemple:

  [u] ρ 

f ↦

      [v] ρ (d ↦ κ (f (d)))si f est une fonction 
      ⊥sinon


Avec cette autre définition, si f n’est pas une fonction, la machine plante avant même d’évaluer v.

5.2  Sauts non locaux et captures de continuations

Voyons maintenant comment on peut coder un mécanisme à la setjmp/longjmp de C. Rappelons que setjmp capture une copie du contexte d’évaluation courant (le compteur de programme, le pointeur de pile, les registres) et la met dans une structure k, que nous représenterons par une variable du λ-calcul, puis retourne 0; longjmp appliqué à k et à une valeur d réinstalle k comme contexte d’évaluation courant, ce qui à pour effet de faire un saut non local à l’endroit du programme où se trouve le setjmp qui à défini k, et de faire retourner la valeur d par ce dernier. Ceci permet de gérer des conditions d’erreur, par ex:

x = setjmp (k);
if (x==0) /* retour normal */
  {
    /* faire un certain nombre de calculs, eventuellement imbriques,
       qui arrivent a la ligne suivante : */
    if (erreur)
      longjmp (k, errno); /* longjmp ne retourne pas,
                             et saute a l'endroit du setjmp ci-dessus */
    /* si pas d'erreur, continuer... */
  }
else
  { /* ici, on est revenu sur erreur, avec x==errno */
    printf ("Erreur no. %d.\n", x);
  }

Comme beaucoup de constructions en C, celle-ci manque de l’élégance des constructions similaires de langages fonctionnels, et la plus proche est une construction, appelée call-with-current-continuation ou call/cc, et qui a été introduite dans le langage Scheme, un dialecte Lisp. (Le manque d’élégance de la construction C est due à deux choses. D’une part, si errno vaut 0, on ne peut pas distinguer un retour normal de setjmp d’un retour sur erreur dans l’exemple ci-dessus. D’autre part, longjmp a une sémantique indéfinie s’il est appelé après que la fonction dans laquelle setjmp a été appelé a retourné.)

L’idée est simple: le concept concret de contexte d’évaluation courant n’est rien d’autre que la continuation κ ! On peut donc définir en première approche une construction callcc k in u qui capture à la façon de setjmp le contexte (la continuation) courante, la met dans k et évalue u; et une construction throw k v qui évalue k et v, et réinstalle la continuation k pour faire retourner la valeur de v par l’instruction callcc qui a défini k. Sémantiquement:

    [callcc k in u] ρ κ=def    [u] (ρ [k:=κ]) κ 
    [throw k v] ρ κ=def[k] ρ (κ′ ↦ [v] ρ κ′)

Remarquer que dans la définition de throw, la continuation κ n’intervient pas dans le côté droit de l’égalité: elle est tout simplement ignorée, car c’est la continuation κ′, valeur de k, qui va être utilisée pour continuer le calcul. Le fait que κ n’intervienne pas signifie que throw k v ne retourne jamais.

On peut tester sur quelques exemples que le comportement est bien le comportement attendu:

Exercice 32   Montrer que callcc k in 3 retourne 3; autrement dit, en supposant que l’on a une constante 3 telle que [3] ρ κ = κ (3), montrer que [callcc k in 3] ρ κ = κ (3).
Exercice 33   Selon cette sémantique, que valent callcc k in throw k 3, callcc k in (throw k 3) 2, callcc k in 1+callcc k' in throw k' (throw k 3) ? (On supposera que 1 et l’addition ont leurs sémantiques naturelles, que l’on précisera.)

Mais en fait, non, le comportement des continuations n’est pas le comportement attendu en général. Considérons le programme callcc k inx · throw ky · y)) 3. On s’attend à ce qu’il ait la même sémantique que le β-réduit callcc k in throw ky · y), autrement dit qu’il retourne la fonction identité, mais ce n’est pas le cas. En effet, formellement:

  
 [callcc k in (λ x · throw       k (λ y · y)) 3] ρ κ 
    =[(λ x · throw k (λ y · y)) 3] (ρ[k:=κ]) κ 
    =[λ x · throw k (λ y · y)] (ρ[k:=κ]) (f ↦ [3] (ρ[k:=κ]) (d ↦ κ (f (d))) 
    =[λ x · throw k (λ y · y)] (ρ[k:=κ]) (f ↦ κ (f (3))) 
    =(f ↦ κ (f (3))) (d ↦ [throw k (λ y · y)] (ρ[k:=κ, x:=d]) id
    =κ ([throw k (λ y · y)] (ρ[k:=κ, x:=3]) id
    =κ ([k] (ρ[k:=κ, x:=3]) (κ′ ↦ [λ y · y] (ρ[k:=κ, x:=3]) κ′)) 
    =κ ((κ′ ↦ [λ y · y] (ρ[k:=κ, x:=3]) κ′) κ) 
    =κ ([λ y · y] (ρ[k:=κ, x:=3]) κ) 
    =κ (κ (d ↦ [y] (ρ[k:=κ, x:=3, y:=d]) id)) 
    =κ (κ (d ↦ d)) 
  

alors que:

  
 [callcc k in throw k (λ y · y)] ρ κ 
    =[throw k (λ y · y)] (ρ[k:=κ]) κ 
    =[k] (ρ[k:=κ]) (κ′ ↦ [λ y · y] (ρ[k:=κ]) κ′) 
    =(κ′ ↦ [λ y · y] (ρ[k:=κ]) κ′) κ 
    =[λ y · y] (ρ[k:=κ]) κ 
    =κ (d ↦ [y] (ρ[k:=κ, y:=d]) id
    =κ (d ↦ d
  

Non seulement les deux valeurs sémantiques ne sont pas identiques — donc nous n’avons plus un modèle correct de la β-réduction —, mais celle de callcc k inx · throw ky · y)) 3 est en fait parfaitement absurde: κ (κ (dd)) signifie que si κ est la suite du programme, au lieu de retourner simplement la fonction dd, on va retourner dd, effectuer la suite du programme, et une fois que le programme aura terminé et retourné une valeur d′, on réexécutera la même suite κ du programme cette fois-ci sur la valeur d′ !

Le problème est dans la façon dont nous avons défini la sémantique de l’abstraction en figure 5, bien que cela ne soit pas facile à voir sur l’exemple. Raisonnons donc par analogie, en identifiant continuation et pile courante (plus compteur de programme). La sémantique de la figure 5 demande à évaluer λ x· u dans la pile κ en retournant une fonction qui doit exécuter u dans la pile id, mais c’est absurde: si jamais la fonction doit être appelée un jour dans le contexte d’une autre pile κ′, alors u doit être évalué dans la pile κ′. Il est donc nécessaire de modifier la définition de sorte qu’elle prenne en paramètre la pile κ′ de l’appelant en plus de la valeur d de l’argument x:

  [λ x · u] ρ κ =def κ (κ′, d ↦ [u] (ρ[x:=d]) κ′)

et la sémantique de l’application uv doit tenir compte de ce changement: la valeur f de u est maintenant une fonction qui prend non seulement la valeur d de v en argument, mais aussi la pile courante κ; f (κ, d) calcule alors la valeur u avec x valant d dans la continuation κ. En particulier, comme κ est passée à f, c’est f (κ, d) qui sera chargée de calculer tout le reste du programme, et on n’a donc pas besoin de calculer κ (f (κ, d)). On pose alors:

  [uv] ρ κ =def [u] ρ (f ↦ [v] ρ (d ↦ f (κ, d)))

En résumé, on a obtenu la sémantique de la figure 6.


      [x] ρ κ=defκ (ρ (x)) 
      [uv] ρ κ=def
[u] ρ 

f ↦ [v] ρ 

d ↦

          f (κ, d)si f est une fonction binaire 
          ⊥sinon




      [λ x · u] ρ κ=def      κ ((κ′, d) ↦ [u] (ρ[x:=d]) κ′) 
      [callcc k in u] ρ κ=def      [u] (ρ [k:=κ]) κ 
      [throw k v] ρ κ=def[k] ρ (κ′ ↦ [v] ρ κ′)
Figure 6: Une sémantique en passage par valeur avec des sauts non locaux

Exercice 34   On pose let x=u in v =defx· v) u. Calculer [let x=u in v] ρ κ.
Exercice 35   On appelle valeur au sens de Plotkin, ou P-valeur, les λ-termes qui sont soit des variables soit des abstractions. Montrer que si V est une P-valeur, [x · u) V] ρ κ = [u [x:=V]] ρ κ. (On dit que la règle v): x · u) Vu [x:=V] est valide.) Montrer que ce résultat ne se généralise pas au cas où V n’est pas une P-valeur.

En Scheme, les constructions callccin et throw ne sont pas présentes telles quelles. Comme k est une variable liée dans callcc k in u, on peut en effet s’arranger pour la lier via un λ, et définir callcc k in u = call/cck· u), où call/cc est un opérateur primitif du langage. La sémantique (provisoire) de call/cc est:

  [call/cc] ρ κ =def κ ((κ′, f) ↦ f (κ′, κ′))

En fait, il est traditionnel lorsqu’on étudie les opérateurs de capture de continuation de faire de la valeur de la variable k de continuation directement une valeur de fonction. En effet, la seule utilisation possible d’une continuation stockée dans k est d’appeler throw k v. Posons k′ =def throw k =def λ x · throw k x: la valeur de k′ est la seule chose que call/cc a besoin de stocker. On définit donc (définitivement):

  [call/cc] ρ κ =def κ ((κ′, f) ↦ f (κ′, (_, d′) ↦ κ′ (d′)))

où (_, d′) ↦ κ′ (d′) est la fonction qui prend une continuation κ″, une valeur d′ et retourne κ′ (d′) après avoir jeté κ″.

Exercice 36   Montrer que, si k est une variable telle que ρ (k) = f, alors [λ x · throw k x] ρ κ = κ ((_, d) ↦ f (d)).
Exercice 37   Vérifier que, si k n’est pas libre dans u, alors [call/cck′ · u)] ρ κ = [callcc k in u [k′:=λ x · throw k x]] ρ κ. (Pour faciliter le calcul, on remarquera que λ x · throw k x est une P-valeur, donc par l’exercice 35, il suffit de montrer que [call/cck′ · u)] ρ κ = [callcc k ink′ · u) (λ x · throw k x)] ρ κ.)
Exercice 38   L’opérateur call/cc connaît de nombreuses variantes, notamment l’opérateur C de Felleisen, dont la sémantique est:
    [ C] ρ κ =def κ ((κ′, f) ↦ f (_ ↦ ⊥, (_, d′) ↦ κ′ (d′)))
_ ↦ ⊥ est la fonction qui retourne toujours . Montrer que, pour tout t:
    [ C t] ρ κ = [t] ρ (f ↦ f (_ ↦ ⊥, (_, d′) ↦ κ (d′)))
Exercice 39   On dit que u retourne normalement dans l’environnement ρ si et seulement si, pour toute continuation κ, [u] ρ κ = κ (d) pour une certaine valeur d, appelée la valeur de u.

Montrer que si u retourne normalement dans ρ[k:=(_, d′) ↦ κ (d′)], alors [ Ck · u)] ρ κ = ⊥. (Autrement dit, si u retourne normalement, alors Ck · u) plante.)

Exercice 40   Montrer que l’on peut réaliser call/cc à l’aide de C. Plus précisément, on montrera que call/cck · u) et Ck · k u) ont la même sémantique.

L’opérateur C est souvent plus pratique pour les manipulations formelles que call/cc. Faisons quelques calculs, dans l’espoir de découvrir quelques égalités sémantiques entre termes. Notamment, que vaut Ck · t) appliqué à v ? Intuitivement, on va récupérer dans k la continuation consistant à appliquer la valeur f de t à la valeur d de v, et ensuite appliquer la continuation courante κ à f (d). Mais on peut récupérer la continuation courante dans une variable k′ en écrivant Ck′ · …), et alors le calcul consistant à calculer t avec comme continuation la fonction qui prend la valeur f de t, l’applique à v et continue le calcul (par k′) est donc t [k:=λ f · k′ (f v)] — ceci du moins tant que v est une P-valeur. Ceci suggère que Ck · t) v = Ck′ · t [k:=λ f · k′ (f v)], au moins si v est une P-valeur. Plus généralement, posons u =def λ k · t, et comparons les deux quantités C u v et Ck′ · uf · k′ (f v))):

  
    [ C u v] ρ κ =    [ C u] ρ (g ↦ [v] ρ (d ↦ g (κ, d))) 
    =[u] ρ (f ↦ f (_ ↦ ⊥, (_, d′) ↦ (g ↦ [v] ρ (d ↦ g (κ, d))) (d′))) 
 par l’exercice 38 
    =[u] ρ (f ↦ f (_ ↦ ⊥, (_, d′) ↦ [v] ρ (d ↦ d′ (κ, d)))) 
  

Tandis que:

  
 [ C (λ k′ · u (λ f · k′ (f v)))] ρ κ 
    =[λ k′ · u (λ f · k′ (f v))] ρ (f ↦ f (_ ↦ ⊥, (_, d′) ↦ κ (d′))) 
    =(f ↦ f (_ ↦ ⊥, (_, d′) ↦ κ (d′))) ((κ′, d″) ↦ [u (λ f · k′ (f v))] (ρ[k′:=d″]) κ′) 
    =[u (λ f · k′ (f v))] (ρ[k′:=(_, d′) ↦ κ (d′)]) (_ ↦ ⊥) 
    =[u] (ρ[k′:=(_, d′) ↦ κ (d′)]) (g ↦ [λ f · k′ (f v)] (ρ[k′:=(_, d′) ↦ κ (d′)]) (d″ ↦ g (_ ↦ ⊥, d″))) 
    =[u] ρ (g ↦ [λ f · k′ (f v)] (ρ[k′:=(_, d′) ↦ κ (d′)]) (d″ ↦ g (_ ↦ ⊥, d″))) 
 à condition que k′ ne soit pas libre dans u 
    =[u] ρ (g ↦ (d″ ↦ g (_ ↦ ⊥, d″)) ((κ′, g′) ↦ [k′ (f v)] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) κ′))
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [k′ (f v)] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) κ′))
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [k] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) 
                                    (f′ ↦ [fv] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d ↦ f′ (κ′, d))))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ 
                          (f′ ↦ [fv] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d ↦ f′ (κ′, d))) ((_, d′) ↦ κ (d′)))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [fv] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d ↦ κ (d)))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [f] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) 
                                    (f′ ↦ [v] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d″ ↦ f′ (d ↦ κ (d), d″))))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ (f′ ↦ [v] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d″ ↦ f′ (d ↦ κ (d), d″))) (g′))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [v] (ρ[k′:=(_, d′) ↦ κ (d′), f:=g′]) (d″ ↦ g′ (d ↦ κ (d), d″)))) 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [v] ρ (d″ ↦ g′ (d ↦ κ (d), d″)))) 
 à condition que k′ et f ne soient pas libres dans v 
    =[u] ρ (g ↦ g (_ ↦ ⊥, (κ′, g′) ↦ [v] ρ (d″ ↦ g′ (κ, d″)))) 
 car (d ↦ κ (d)) = κ 
  

On a donc démontré:

Lemme 7   [ C u v] ρ κ = [ Ck′ · uf · k′ (f v)))] ρ κ, où k et f sont deux variables nouvelles.

Notons que ceci est correct même quand v n’est pas une P-valeur. On peut directement formaliser le comportement de l’opérateur C par des règles de réécriture inspirées de la sémantique:

  
    ( CL) C u v C (λ k′ · u (λ f · k′ (f v))) 
    ( CR)V ( C u) C (λ k′ · u (λ x · k′ (V x)))

où dans la seconde, V est une P-valeur.

Exercice 41 ()   Montrer que la règle ( CR) est valide. (On utilisera le fait que toute P-valeur V retourne normalement, cf. exercice 39, et en fait que [V] ρ κ = κ (d) pour une valeur d qui ne dépend que de V et de ρ.) Montrer que l’hypothèse selon laquelle V est une P-valeur est essentielle.

Nous avons ignoré les problèmes de définition de domaine de valeurs. Mais ceci se règle aisément. Nous avons besoin d’un domaine D, les continuations κ seront des éléments de [DD], et les valeurs des fonctions seront des éléments de [[DD] × DD]. Il suffit donc de prendre un domaine D tel que:

  D = ( N ⋃ [[D → D] × D → D] )

à isomorphisme près, et où N peut être remplacé par n’importe quel autre ensemble de valeurs de base. (Noter que les continuations ne sont pas incluses dans le domaine des valeurs, car les continuations capturées par C sont codées comme des fonctions dans [[DD] × DD].)

5.3  Ajout d’effets de bord

()

Nous allons maintenant voir quelques conséquences intéressantes de cette façon de voir la sémantique des langages en appel par valeur.

La première est qu’il est maintenant facile de donner une sémantique à un langage enrichi d’effets de bord, sous la forme d’affectations. Voici, dans le style de ML, les constructions que nous voulons ajouter: certains termes dénoteront des références, c’est-à-dire des adresses en mémoire pointant vers des valeurs; la construction !u retourne la valeur stockée à l’adresse u, la construction u:=v stocke la valeur de v à l’adresse u, et enfin ref u alloue une adresse l, y stocke la valeur de u, et retourne l. Tant qu’à imiter les langages impératifs comme Pascal ou C, nous allons aussi introduire une construction de séquencement u;v qui évalue u, puis évalue et retourne la valeur de v.

Pour donner une sémantique formelle à ces constructions, on peut modifier notre fonction [_] de sorte qu’elle prenne maintenant non seulement un environnement ρ, une continuation κ, mais aussi une mémoire σ ∈ [LD], où L est un ensemble d’adresses (“locations” en anglais).

Noter que les continuations doivent maintenant non seulement prendre en argument la valeur calculée par un terme u, mais aussi le nouvel état de la mémoire. En résumé, on aura les conventions de typage:

    ρ V → D 
    σ[L→ D
    κ[[L→ D] × D → A]

A est un domaine de réponses. Auparavant, A était juste le domaine D des valeurs, mais il est aussi envisageable de vouloir prendre pour A le produit du domaine D des valeurs avec celui, [LD], des mémoires, par exemple, selon ce que l’on souhaite observer à la fin du calcul.

Les valeurs de fonctions devront maintenant prendre une continuation, une valeur pour son argument, et une mémoire courante, et retourner une réponse. Notre domaine sémantique doit aussi être enrichi par des adresses, et c’est pourquoi nous posons:

    D=defN ⊕ L ⊕ [Cont × Mem × D → A] )
    Cont=def[Mem × D → A
    Mem=def[L→ D]

N pourrait être remplacé par n’importe quel autre ensemble de valeurs de base. On peut alors définir notre sémantique comme en figure 7. (Exercice: vérifier que la définition est cohérente avec le typage donné par les équations de domaine pour D, Cont et Mem.)


      [x] ρ σ κ=defκ (σ, ρ (x)) 
      [uv] ρ σ κ=def
[u] ρ σ

(σ′, f) ↦ [v] ρ σ′

(σ″, d) ↦

              f (κ, σ″, d)si f est une fonction 
              ⊥sinon




      [λ x · u] ρ σ κ=def      κ (σ, (κ′, σ′, d) ↦ [u] (ρ[x:=d]) σ′ κ′) 
      [!u] ρ σ κ=def
[u] ρ σ

(σ′, l) ↦

            κ (σ′, σ′ (l))            si l est dans le domaine de σ′ 
            ⊥sinon


      [u:=v] ρ σ κ=def
      [u] ρ σ 

(σ′, l) ↦ [v] ρ σ′ 

(σ″, d) ↦

              κ (σ″[l:=d], d)si l∈ L 
              ⊥sinon




      [ref u] ρ σ κ=def      [u] ρ σ ((σ′, d) ↦ κ (σ′[l:=d], l))      (l hors du domaine de σ′) 
      [u;v] ρ σ κ=def      [u] ρ σ ((σ′, _) ↦ [v] ρ σ′ κ)
Figure 7: Une sémantique en passage par valeur avec affectations

Exercice 42   Commenter la sémantique de la figure 6 en langage courant. En particulier, dire dans quel ordre cette sémantique spécifie que doivent être évaluées les expressions, dans quelles continuations et quelles mémoires elles sont évaluées.

On peut bien sûr encore donner une sémantique à l’opérateur C dans ce contexte:

  [ C] ρ σ κ =def κ (σ, (κ′, σ′, f) ↦ f (_ ↦ ⊥, σ′, (_, σ″, d′) ↦ κ′ (σ″, d′)))
Exercice 43   Soit * un identificateur distingué (le handler d’exception courant). On définit les constructions suivantes:
      try u with x ⇒ v=deflet k0=!* in 
        C (λ k · *:=(λ x · *:=k0k v); 
            k (let y=u in *:=k0y))
      raise v=def!* v
(Voir l’exercice 34 pour la construction let.) Justifier intuitivement pourquoi il s’agit bien d’une gestion d’exceptions à la OCaml — et sous quelles conditions.

Alors qu’il était possible, quoique difficile, de trouver une sémantique par réduction pour C (les règles LL et LR), il est pratiquement infaisable d’en trouver une satisfaisante pour les affectations.


Previous Up Next