• Aucun résultat trouvé

L’utilisation de combinateurs est une mani`ere de r´esoudre le probl`eme P1. En effet, on peut transformer tout programme du λ-calcul pur en un programme ´equivalent2compos´e uniquement d’applications entre trois fonctions ´el´ementaires not´ees S, K et I [CF58]. Cette transformation est appel´ee SK-traduction ou SK-compilation. Nous l’appellerons ´egalement combinatorisation. La SK-traduction est donc un moyen de cr´eer de nouvelles fonctions par applications de fonctions. Dans cette section nous consid´erons une version simplifi´ee du langage, sans les fonctions non-strictes et sans les traits imp´eratifs. Les sections V.5 et V.6 s’attacheront `a les int´egrer dans le sch´ema d’´evaluation que nous proposons ici.

V.4.1 SK-traduction classique

L’objet de la SK-traduction est de d´ecomposer toute fonction en une combinaison de fonctions ´el´ementaires [CF58]. Consid´erons la fonction λx.(e1 e2). La variable x peut apparaˆıtre dans e1 et dans e2. Lorsqu’on abstrait x dans (e1 e2) on abstrait donc x dans e1 et dans e2. L’´egalit´e suivante illustre bien ceci :

((λx.e1 e2) a) = ((λx.e1 a) (λx.e2 a))

C’est sur cette ´egalit´e qu’est bas´ee ce que l’on appelle la transformation S : pour abstraire x dans une application (e1 e2) il est suffisant de l’abstraire dans e1 et dans e2 et d’utiliser une fonction ´el´ementaire charg´ee de distribuer l’argument re¸cu par cette fonction `a chacune des nouvelles abstractions puis effectuer l’application. La fonction ´el´ementaire distribuant l’argument re¸cu est le combinateur S. La r`egle de transformation S ainsi que la r`egle de r´eduction associ´ee sont donn´ees dans la figure 2.

Le combinateur S est la fonction λf.λg.λx.((f x) (g x)). Le r´esultat de la transformation S est bien compos´e de plusieurs fonctions simples : λx.e1ainsi que λx.e2sont bien plus simples que

2

Attention, l’´equivalence dont on parle ici est `a nuancer. En effet, selon [HS86, pp 30 et suivantes], le λ-calcul et la logique combinatoire ont des propri´et´es ´equivalentes. De plus, on peut d´efinir une notion d’´equivalence faible (que l’on notera ≡F), c’est-`a-dire une relation d’´equivalence sur les termes de la logique combinatoire ´equivalents par conversion faible.

Cependant, le λ-calcul poss`ede une propri´et´e appel´ee ξ par [CF58] qui est : si M βηN alors (λx.X)βη(λx.Y ). Cette propri´et´e est vraie car toute contraction ou substitution de variable li´ee faite dans X peut aussi ˆetre faite dans (λx.X). Or, ce n’est pas le cas en logique combinatoire. Traduite dans le langage des combinateurs, cette propri´et´e devient : si M ≡

F N alors (λ∗x.X) ≡

F (λ∗x.Y ). Ce qui n’est pas le cas (le lieur ne fait pas partie du langage). Par exemple, si X ≡ S xyz et Y ≡ xz(yz), alors X ≡

F Y mais on a (λ∗x.X) ≡ S(S S(K y))(K z) et (λx.Y ) ≡ S(S I(K z))(K(yz)), qui sont deux termes en forme normale distincte, donc non faiblement ´equivalents. Le lecteur int´eress´e se reportera `a la discussion de [HS86, chap. 9, pp 87 et suivantes] qui discute ce point. Pour notre usage, l’absence de ξ n’est pas un point gˆenant.

transformation r´eduction impl´ementation S λx.(e1 e2) ⇒ S (λx.e1) (λx.e2) S f g e → (f e) (g e) λx.λy.λz.((x z) (y z)) K λx.c ⇒ K c K c e → c λx.λy.x

I λx.x ⇒ I I e → e λx.x

Dans la transformation K, c d´esigne une constante (un entier, une fonction externe ou un com-binateur) ou une variable autre que x. Le symbole ⇒ d´enote une ´etape de combinatorisation. Le symbole → d´enote une ´etape de r´eduction.

Fig. 2 – SK-traduction pure.

λx.(e1 e2) et S est une constante du langage hˆote. On dira que la transformation S fait descendre les lambdas car le lambda en tˆete du terme s’est vu d´eplac´e dans les sous-termes de celui-ci. En it´erant la transformation S pour faire descendre tous les lambdas, on arrive `a des termes o`u les abstractions restantes sont soit de la forme λx.x, soit λx.c o`u c est soit une constante, soit une variable autre que x. La premi`ere forme correspond `a une fonction ´el´ementaire (un combinateur) que l’on appelle I. La seconde peut ˆetre d´ecompos´ee en ((λv.λx.v) c). La fonction λv.λx.v est la troisi`eme fonction ´el´ementaire et nous l’appelons K. La forme λx.c se transforma donc en (K c). La forme (K c) signifie que l’argument attendu par ce terme est inutile et sera donc « d´elaiss´e » mais il est toutefois attendu puisque la transformation S a plac´e un lambda directement au dessus de c. Si le terme compil´e est clos, les abstractions mises sous la forme (K x) auront disparu `a la fin du processus car x est li´ee par un lambda qui va « descendre » jusqu’`a elle.

L’algorithme de SK-traduction consiste `a appliquer les r`egles de transformation de la figure 2 tant qu’il reste des abstractions dans le terme. Les r`egles sont traditionnellement appliqu´ees en commen¸cant par les abstractions les plus internes du terme `a transformer (strat´egie inner-most).

`

A la fin il ne reste donc plus que des applications entre constantes (les combinateurs S, K, I et les autres fonctions externes ´etant des constantes). L’algorithme peut ˆetre d´efini ´egalement par la fonction C suivante qui parcourt le terme `a compiler et effectue les transformations :

C[(e1 e2)] = (C[e1] C[e2]) C[c] = c

C[λx.e] = Ahx, C[e]i

Ahx, xi = I Ahx, ci = (K c)

Ahx, (e1 e2)i = S Ahx, e1i Ahx, e2i Dans la d´efinition de C, c d´esigne une constante (enti`ere ou fonction externe) ou une variable. La fonction auxiliaire A compile l’abstraction d’un terme par rapport `a une variable. Sous cette forme, l’algorithme applique les r`egles en profondeur d’abord. La fonction C propage la traduction tandis que la fonction A introduit les combinateurs (les transformations S, K et I portent toutes sur des abstractions). Puisque A n’est appliqu´ee qu’`a une forme compil´ee, la d´efinition de A ne pr´evoit pas le cas Ahx, λy.ei. Exemple de fonctionnement de l’algorithme :

C[λx.((+ x) 1)] = Ahx, C[((+ x) 1)]i = Ahx, (C[(+ x)] C[1])i = Ahx, ((C[+] C[x]) 1)i = Ahx, ((+ x) 1)i

= (S Ahx, (+ x)i Ahx, 1i) ( = S λx.(+ x) λx.1 ) = (S (S Ahx, +i Ahx, xi)) (K 1)) ( = S (S λx.+ λx.x) (K 1) ) = (S (S (K +) I)) (K 1))

94 SK-traduction au vol en pr´esence de traits imp´eratifs

Nous appellerons SK-traduction pure cet algorithme de traduction. L’algorithme donn´e par les fonctions C et A est proche de son impl´ementation. Toutefois nous pr´ef´erons le d´ecrire par en ensemble de r`egles de r´ecriture comme celles de la figure 2 accompagn´e d’une strat´egie d’appli-cation (ici la strat´egie inner-most). Par cons´equent, dans la suite du document nous d´ecrirons les modifications `a cet algorithme en d´ecrivant les modifications `a l’ensemble de r`egles ou `a la strat´egie d’application.

V.4.2 Impl´ementation

Les combinateurs S, K et I sont impl´ement´es par des fonctions du langage hˆote du type value -> valueet peuvent donc ˆetre utilis´es comme fonctions pr´ed´efinies dans le type HOterm introduit en section pr´ec´edente :

let app f x = match f with ValFun f’ -> f’ x let comb I = fun x -> x

let comb K = fun c -> ValFun (fun x -> c)

let comb S = fun f -> ValFun (fun g -> ValFun (fun x -> app (app f x) (app g x) ))

Exemple : la fonction MGS d´efinie par fun (x) = 1 qui se SK-traduit en (K 1) est repr´esent´ee par l’expression de combinateurs HOApp(HOFun comb K, HOInt 1).

L’´evaluation des expressions de combinateurs (type HOterm) suit le sch´ema d’´evaluation pr´esent´e en section pr´ec´edente (fonction evalHO).

R´esultats

Le sch´ema d’´evaluation mis en place est plus lent que le sch´ema classique pr´esent´e en sec-tion V.2 (voir mesures en secsec-tion VI.4.1). En effet, la SK-traducsec-tion pure fait exploser la taille du terme transform´e, menant ainsi `a une interpr´etation inefficace. Toutefois, des optimisations de la SK-traduction permettent de contenir l’explosion de la taille du terme produit. Nous ´etudierons dans le chapitre suivant trois optimisations nous permettant d’atteindre des performances rai-sonnables.

Discussion 2 L’utilisation d’un type somme (ici le type HOterm) pour repr´esenter les termes combinatoris´es est une source d’inefficacit´e. En effet le programme dans cette forme passe beau-coup de temps `a

– produire des valeurs construites comme HOInt 0 ;

– d´econstruire des valeurs construites, par exemple pour acc´eder au 0 de HOInt 0.

L’impl´ementation de l’application d’un terme de ce type illustre bien la perte de temps li´ee `a la d´econstruction. La fonction app donn´ee ci-dessus effectue l’application d’un terme f `a un terme x. Cette fonction effectue deux actions : elle extrait le fonction du langage hˆote embarqu´ee dans la valeur construite f puis elle l’applique `a x en d´el´eguant cette application au langage hˆote. On voit bien que toute application requiert une d´econstruction.

Par cons´equent, il serait plus efficace de ne pas utiliser de types sommes. Par exemple on a naturellement envie d’impl´ementer les combinateurs comme suit :

I : fun x -> x

K : fun x -> fun y -> x

S : fun f -> fun g -> fun x -> (f x) (g x)

On pourrait avoir cette approche si on compilait vers OCaml. En effet, dans ce cas le pro-gramme produit serait une chaˆıne de caract`eres (type string). En revanche, lorsqu’on produit directement des valeurs du langage hˆote on se heurte au probl`eme suivant : les programmes `a combinatoriser n’ont pas tous le mˆeme type. En effet on peut avoir besoin de produire la valeur 1 comme la valeur fun x -> x. ´Evidemment, une telle fonction de combinatorisation sera rejet´ee par le syst`eme de type du langage hˆote. Deux solutions sont envisageables : utiliser un type somme ou utiliser un langage hˆote faiblement typ´e.

Ce probl`eme est ind´ependant de l’approche par syntaxe d’ordre sup´erieur. En effet, il est d´ej`a pr´esent dans l’approche classique d’impl´ementation des interpr`etes propos´ee en section V.2. Par cons´equent nous ne reviendrons plus sur ce point dans ce chapitre.