• Aucun résultat trouvé

Expression Logique et Fonctionnelle... Évidemment

N/A
N/A
Protected

Academic year: 2022

Partager "Expression Logique et Fonctionnelle... Évidemment"

Copied!
9
0
0

Texte intégral

(1)

USTL - Licence info 3ème année 2007-2008

Expression Logique et Fonctionnelle . . . Évidemment

Peut-on tout décrire avec des fonctions ?

Le but de ces quelques notes est de montrer qu'il est possible de représenter les nombres entiers, les booléens et les couples par des fonctions pures, et de se convaincre que tout ce qui est programmable peut l'être avec le seul formalisme des fonctions. La base théorique sous-jacente est leλ-calcul introduit en 1932 par Alonzo Church pour tenter de cerner la notion de calculabilité.

Tout est exprimé dans le langage Caml, en s'interdisant le recours aux nombres, booléens etn-uplets de ce langage, ainsi qu'aux formes if et let rec, et les opérateurs ou fonctions prédénies. Les seuls éléments du langage que nous nous autoriserons sont les identicateurs de variables, l'application d'une fonction à un argument et la construction d'une fonction (forme function x -> ...). Par commodité, nous nous autoriserons aussi la formeletpour nommer certains termes.

1 Les entiers

1.1 Représentation de Church

Observons la représentation des quatre premiers nombres entiers 0, 1, 2 et 3 donnée par les dénitions suivantes :

(* l’entier zéro *)

let zero = fun f x -> x ;;

(* l’entier un *)

let un = fun f x -> f x ;;

(* l’entier deux *)

let deux = fun f x -> f (f x) ;;

(* l’entier trois *)

let trois = fun f x -> f (f (f x)) ;;

Chacune de ces dénitions indique que l'entier n est représenté comme une fonction qui itère une certaine fonctionfnfois depuis une donnéex.

Plus généralement, la représentation de Church d'un entier n∈Npeut être donnée par l'expression fun f x -> f (f ... (f x) ;;

dans laquellefapparaîtnfois.

Application : Cette représentation d'un entiern peut être utilisée pour itérer n fois une fonctionf depuis un élémentx. Par exemple, en prenant pour fonctionf la fonction prédéniesuccet pour élément xl'entier (de Caml) 0, on a

# zero succ 0 ;;

- : int = 0

# un succ 0 ;;

- : int = 1

# deux succ 0 ;;

- : int = 2

# trois succ 0 ;;

- : int = 3

Cela suggère la fonction de conversion d'un entier représenté sous forme de Church en un entier de Caml.

(2)

Remarque : le type général de la représentation de Church d'un entier est (’a -> ’a) -> ’a -> ’a.

Toutefois, le type de zero est’a -> ’b -> ’b et celui deun est (’a -> ’b) -> ’a -> ’b. Le type (’a -> ’a) -> ’a -> ’aest l'unicateur le plus général de ces deux types.

1.2 La fonction successeur

La fonction successeur doit transformer un entiernen l'entiern+1. Lorsque les entiers sont représentés sous forme de Church, cela revient à ajouter une application supplémentaire de la fonction intervenant dans la représentation. Cet ajout peut se faire de deux manières :

1. on applique la fonction fau résultat de l'itération let suc1 = fun n f x -> f (n f x) ;;

2. on applique la fonction fà l'élémentxavant d'appliquern let suc2 = fun n f x -> n f (f x) ;;

# List.map entier2int (List.map suc1 [zero; un; deux; trois]) ;;

- : int list = [1; 2; 3; 4]

# List.map entier2int (List.map suc2 [zero; un; deux; trois]) ;;

- : int list = [1; 2; 3; 4]

Remarques :

1. Le type de la fonctionsuc1 est ((’a -> ’b) -> ’c -> ’a) -> (’a -> ’b) -> ’c -> ’bet celui de la fonction suc2 est ((’a -> ’b) -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c. Chacun de ces types est bien plus général que((’a -> ’a) -> ’a -> ’a) -> (’a -> ’a) -> ’a -> ’a.

2. Les entiers donnés par ces deux fonctions ne sont pas de type (’a -> ’a) -> ’a -> ’amais de type(’_a -> ’_a) -> ’_a -> ’_a.

# let quatre1 = suc1 trois ;;

val quatre1 : (’_a -> ’_a) -> ’_a -> ’_a = <fun>

# let quatre2 = suc2 trois ;;

val quatre2 : (’_a -> ’_a) -> ’_a -> ’_a = <fun>

La variable de type ’_a indique que le type de quatre1 est en attente d'instanciation. C'est la première utilisation dequatre1 qui xera la valeur de’_a.

# quatre1 ;;

- : (’_a -> ’_a) -> ’_a -> ’_a = <fun>

# quatre1 succ ;;

- : int -> int = <fun>

# quatre1 ;;

- : (int -> int) -> int -> int = <fun>

1.3 L'addition

Pour dénir la somme de deux entiers netm, on peut s'y prendre de deux façons :

1. on peut considérer que la somme n+mest obtenue en itérantnfois la fonction successeur surm let add1 = fun n m -> n suc1 m ;;

2. ou si on préfère ne pas utiliser la fonction successeur, la sommen+mpeut être vue comme l'itération nfois d'une fonction f sur un élément obtenu en itérantmfois la même fonctionf

let add2 = fun n m f x -> n f (m f x) ;;

(3)

# List.map entier2int (List.map (add1 zero) [zero; un; deux; trois]) ;;

- : int list = [0; 1; 2; 3]

# List.map entier2int (List.map (add1 un) [zero; un; deux; trois]) ;;

- : int list = [1; 2; 3; 4]

# List.map entier2int (List.map (add1 deux) [zero; un; deux; trois]) ;;

- : int list = [2; 3; 4; 5]

# List.map entier2int (List.map (add1 trois) [zero; un; deux; trois]) ;;

- : int list = [3; 4; 5; 6]

1.4 La multiplication

Le produit de deux entiers netmest l'itération nfois de la fonctionf itéréem fois.

let mult = fun n m f -> n (m f);;

# List.map entier2int (List.map (mult zero) [zero; un; deux; trois]) ;;

- : int list = [0; 0; 0; 0]

# List.map entier2int (List.map (mult un) [zero; un; deux; trois]) ;;

- : int list = [0; 1; 2; 3]

# List.map entier2int (List.map (mult deux) [zero; un; deux; trois]) ;;

- : int list = [0; 2; 4; 6]

# List.map entier2int (List.map (mult trois) [zero; un; deux; trois]) ;;

- : int list = [0; 3; 6; 9]

1.5 L'exponentiation

L'exponentiation mn est encore plus simple à dénir puisqu'on l'obtient en itérantnfois l'entier m. let exp = fun n m -> n m;;

# List.map entier2int (List.map (exp zero) [zero; un; deux; trois]) ;;

- : int list = [1; 1; 1; 1]

# List.map entier2int (List.map (exp un) [zero; un; deux; trois]) ;;

- : int list = [0; 1; 2; 3]

# List.map entier2int (List.map (exp deux) [zero; un; deux; trois]) ;;

- : int list = [0; 1; 4; 9]

# List.map entier2int (List.map (exp trois) [zero; un; deux; trois]) ;;

- : int list = [0; 1; 8; 27]

1.6 Et la soustraction ?

La soustraction va nécessiter de dénir les booléens et les couples.

2 Les booléens

2.1 Représentation des deux booléens

Nous dénirons les deux valeurs booléennes par (* Le vrai *)

let vrai = fun x y -> x ;;

(* le faux *)

let faux = fun x y -> y ;;

Ces dénitions se justieront par l'usage que nous en ferons pour dénir la conditionnelle.

La fonction booleen2boolconvertit les termesvraiet fauxen les booléens de Caml.

(4)

Remarque : Le booléen faux est représenté par le même terme que l'entier 0.

Remarque : le type de vrai est ’a -> ’b -> ’a et celui defaux ’a -> ’b -> ’b. Le type le plus général qui unie ces deux types est’a -> ’a -> ’a.

2.2 La conditionnelle

La fonction conditionnelle permet d'exprimer la forme si ... alors ... sinon .... Le choix de la représentation des booléens rend très aisée la dénition de cette fonction.

(* la fonction conditionnelle

* cond c a s = a si c est vrai

* = s sinon

*)

let cond = fun c a s -> c a s ;;

# cond vrai 1 2;;

- : int = 1

# cond faux 1 2;;

- : int = 2

Remarque : s'il est facile de dénir la fonction conditionnelle, son utilisation pose néanmoins le prob- lème de l'évaluation d'une expression conditionnelle. Si pour évaluer un appel de fonction, on emploie la stratégie d'évaluation préalable de tous ses arguments, il peut arriver certaines dicultés. OCaml adopte l'évaluation complète des arguments d'un appel de fonction et on comprend pourquoi l'évaluation de l'expressioncond vrai 1 1/0, qui vaut1, pose problème.

# cond vrai 1 1/0;;

Exception: Division_by_zero.

Pour éviter ce genre de problème, il est nécessaire d'adopter une autre stratégie nommée évaluation paresseuse qui consiste à n'évaluer les expressions que lorsqu'on y est obligé.

2.3 Les opérateurs logiques

La fonction condpermet une dénition aisée des opérateurs booléens.

(* l’opérateur de négation *)

let non = function b -> cond b faux vrai ;;

(* la conjonction *)

let et = fun b1 b2 -> cond b1 b2 faux ;;

(* la disjonction *)

let ou = fun b1 b2 -> cond b1 vrai b2 ;;

# List.map booleen2bool (List.map non [vrai ; faux]) ;;

- : bool list = [false; true]

# List.map booleen2bool (List.map (et vrai) [vrai ; faux]) ;;

- : bool list = [true; false]

# List.map booleen2bool (List.map (et faux) [vrai ; faux]) ;;

- : bool list = [false; false]

# List.map booleen2bool (List.map (ou vrai) [vrai ; faux]) ;;;

- : bool list = [true; true]

# List.map booleen2bool (List.map (ou faux) [vrai ; faux]) ;;;

- : bool list = [true; false]

(5)

2.4 Un test de nullité

On est en mesure maintenant de dénir le test de nullité d'un entier.

let estNul = function n -> n (function x -> faux) vrai ;;

# List.map booleen2bool (List.map estNul [zero; un; deux; trois]) ;;

- : bool list = [true; false; false; false]

3 Les couples

Par dénition, un couple contient deux choses. On doit pouvoir construire un couple à partir de n'importe quelles deux choses, et extraire l'une ou l'autre des choses contenues dans un couple.

Le couple (x, y)peut être représenté par la fonctionfunction z -> z x y. La conversion de ces couples en couples de Caml s'obtient avec la fonction let couple2caml = function c -> (c vrai),(c faux) ;;

Remarque : le type le plus général d'un couple est donc(’a -> ’b -> ’c) -> ’c.

3.1 Le constructeur

Le constructeur de couple est la fonction cons. let cons = fun x y -> function z -> z x y ;;

# cons 1 2;;

- : (int -> int -> ’_a) -> ’_a = <fun>

# couple2caml (cons 1 2) ;;

- : int * int = (1, 2)

Remarque : le champ de notre étude est indépendant de la notion de type (c'est du λ-calcul pur).

Cependant, l'utilisation de OCaml amène quelques restrictions d'usage de la fonctioncouple2caml.

# cons 1 true ;;

- : (int -> bool -> ’_a) -> ’_a = <fun>

# couple2caml (cons 1 true) ;;

Characters 12-25:

couple2caml (cons 1 true) ;;

^^^^^^^^^^^^^

This expression has type (int -> bool -> ’a) -> ’a but is here used with type (int -> int -> int) -> ’b

3.2 Les sélecteurs

Les sélecteurs des composantes d'un couple s'obtiennent aisément avec les booléens.

(* sélection de la première composante *) let car = function c -> c vrai ;;

(* sélection de la seconde composante *) let cdr = function c -> c faux ;;

# entier2int (car (cons un deux)) ;;

- : int = 1

# entier2int (cdr (cons un deux)) ;;

(6)

3.3 Prédécesseur d'un entier

Avec les couples il est possible de calculer le prédécesseur d'un entier n. Il sut d'itérer n fois la fonction qui à un couple(a, b)associe le couple(b, b+ 1) à partir du couple(0,0).

(* prédécesseur

* pred n = entier qui précède n

*)

let pred = function n ->

car (n (function c -> cons (cdr c) (suc1 (cdr c))) (cons zero zero));;

# List.map entier2int (List.map pred [zero; un; deux; trois]) ;;

- : int list = [0; 0; 1; 2]

Remarque : Avec cette dénition,zeroest son propre prédécesseur.

3.4 Soustraction

La soustraction peut se dénir par itération du prédécesseur.

let sub = fun n m -> m pred n ;;

3.5 Test d'égalité

Une idée naturelle pour décrire la fonction d'égalité de deux entiers est de tester la nullité de la diérence de ces deux entiers. Mais cette idée ne peut s'appliquer en utilisant la fonction sub car elle attribue la valeurzero àn−mlorsquen≤m.

let inf = fun n m -> estNul (sub n m) ;;

let egal = fun n m -> et (inf n m) (inf m n) ;;

4 La récursivité

4.1 Factorielle avec des couples

On peut calculer n!en itérantnfois la fonction(a, b)7→(a+ 1,(a+ 1)b)depuis le couple(0,1). let fact = function n ->

cdr (n (function c -> (cons (suc1 (car c)) (mult (suc1 (car c)) (cdr c) ))) (cons zero un)) ;;

# List.map entier2int (List.map fact [zero; un; deux; trois]) ;;

- : int list = [1; 1; 2; 6]

# entier2int (fact (exp deux trois)) ;;

- : int = 362880

4.2 Point xe

On imagine bien pouvoir écrire, de la même façon que la fonction fact ci-dessus, toute fonction que l'on programmerait autrement à l'aide d'une boucle pour1. Mais qu'en estil pour les fonctions qui n'entrent pas dans ce cadre2?

Nous allons proposer une autre solution pour la fonction factorielle qui suit un schéma général ap- plicable à toute fonction. Cette approche est celle du calcul du plus petit point xe d'une fonctionnelle, point xe qui peut être déterminé par un opérateur nommé combinateur de point xe.

1Dans la terminomogie des fonctions récursives, ce sont les fonctions primitives récursives

2Comme par exemple, le calcul du pgcd de deux entiers, ou encore le calcul de la longueur d'une liste.

(7)

4.2.1 L'ensemble ordonné F

Notons F=NN l'ensemble de toutes les fonctions deNdansN. Les fonctions considérées ici ne sont pas nécessairement dénies sur N tout entier, c'estàdire qu'étant donné f ∈ F, il peut exister des valeurs den∈Ntelles quef(n)ne soit pas déni. Nous noterons⊥la (seule) fonction deF dénie nulle part.

Voici écrite en Caml3 la fonction⊥: let bottom = function x ->

(function y -> y y) (function y -> y y);;

Toute tentative d'application de la fonctionbottomà un quelconque argument conduit à une évaluation innie.

# bottom 1 ;;

Interrupted.

On dira qu'une fonction f ∈ F est moins dénie qu'une fonction g ∈ F, et on notera f 4 g, si pour tout entier noù la fonctionf est dénie, on a f(n) =g(n), autrement dit si f et g coincident sur l'ensemble de dénition def. La relation4sur l'ensembleF en fait un ensemble non totalement ordonné admettant la fonction ⊥ comme plus petit élément, et les fonctions partout dénies comme éléments maximaux.

4.2.2 Fonctionnelles

Nous appellerons fonctionnelle toute application deF dans lui-même. Par exemple, la fonctionnelle Φfactdénie par

Φfact : F −→ F

f 7−→ g= Φfact(f) oùgest la fonction dénie par

g(n) =

1 sin= 0

n×f(n−1) sinon . AppliquonsΦfact à la fonction⊥

f1= Φfact(⊥).

Par la dénition même deΦfact, la fonction f1 est dénie en 0 et vaut 1. Et elle n'est dénie nulle part ailleurs, étant donné que⊥n'est dénie nulle part.

Appliquons maintenantΦfactà f1

f2= Φfact(f1).

Cette fonctionf2est dénie en 0 où elle prend la même valeur quef1, et en 1 où elle vaut 1. Ainsif1est moins dénie quef2. D'autre part, f2 n'est dénie pour aucun entier supérieur à 1.

Si on considère la suite de fonctions(fn)n∈NdeF dénie par récurrence par f0 = ⊥

fn+1 = Φfact(fn) on obtient des fonctions telles que

1. fn(k) =k!sik < n;

2. etfn(k)non déni sik≥n.

On peut remarquer que cette suite est constituée de fonctions de mieux en mieux dénies f04f14f24. . .4fn4. . .

autrement dit la suite est croissante dans F, et que la fonction factorielle (fact) est mieux dénie que toutes les fonctions de cette suite,

∀n∈N fn4fact.

On peut dénir tout cela en Caml4 par

3Par défaut, OCaml refuse la dénition debottomcar l'expressiony y n'est pas typable. Il faut autoriser les types récursifs (option-rectypesde la commandeocaml) pour que Caml accepte cette dénition en lui donnant alors le type’a

(8)

let phiFact = fun f n ->

if n=0 then 1 else n * f (n - 1) ;;

let f0 = bottom ;;

let f1 = phiFact f0 ;;

let f2 = phiFact f1 ;;

let f3 = phiFact f2 ;;

let f4 = phiFact f3 ;;

et on peut vérier que les fonctions ainsi dénies sont des fonctions qui approchent de mieux en mieux la fonction factorielle.

# List.map f2 [0;1] ;;

- : int list = [1; 1]

# List.map f3 [0;1;2] ;;

- : int list = [1; 1; 2]

# List.map f4 [0;1;2;3] ;;

- : int list = [1; 1; 2; 6]

# f4 4 ;;

Interrupted.

4.2.3 Point xe

Quelle est la fonction obtenue si on applique la fonctionnelleΦfactà la fonctionfact? On peut vérier que l'on a

Φfact(fact) = fact.

Trichons un peu et vérions cette égalité :

# let rec fact n = if n=0 then 1 else n*fact(n-1) ;;

val fact : int -> int = <fun>

# List.map fact [1;2;3;4;5;6;7;8;9;10] ;;

- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]

# let fact2 = phiFact fact ;;

val fact2 : int -> int = <fun>

# List.map fact2 [1;2;3;4;5;6;7;8;9;10] ;;

- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]

Autrement dit, la fonction factest un point xe de la fonctionnelleΦf act. Une fonctionf ∈ F est un point xe d'une fonctionnelleΦsi on a l'égalité

Φ(f) =f.

Remarque : Deux questions naturelles se posent

1. est-ce que toute fonctionnelle admet des points xes ? 2. y a-t-il unicité du point xe ?

La réponse à ces deux questions est négative en général. L'existence est assurée si la fonctionnelle satisfait une propriété de monotonie, c'estàdire siΦ(f)4Φ(g)dès lors que f 4g5. Φfact est une fonctionnelle monotone, et la fonctionfactest donc son (plus petit) point xe.

4.2.4 Combinateur de point xe

Existe-t-il un moyen de calculer le point xe d'une fonctionnelle (s'il existe). La réponse est armative, et il existe plusieurs opérateurs (ou fonctions ou algorithmes) permettant de les calculer. On les appelle combinateurs de point xe.

En voici un, nommé combinateur de Curry, dont nous donnons la dénition en Caml.

5pour en savoir plus à ce sujet cf A COMPLETER

(9)

let fixCurry = function f ->

(function x -> f (x x)) (function x -> f (x x)) ;;

Vérions quefixCurryest un combinateur de point xe. SoitΦune fonctionnelle.

fixCurryΦ = (function f -> (function x -> f (x x)) (function x -> f (x x)))Φ

(function x -> Φ (x x)) (function x -> Φ (x x))

→ Φ((function x -> Φ (x x)) (function x -> Φ (x x)))

= Φ(fixcurryΦ).

La èche→indique une étape de réduction.

Ainsi, le termefixCurryΦest un point xe deΦ.

Modions la dénition précédente de fixCurrypour le rendre opérationnel en Caml.

let fixCurry = function f ->

(fun x y -> f (x x) y) (fun x y -> f (x x) y) ;;

Appliquons ce combinateur àΦfact let fact = fixCurry phiFact ;;

et vérions le point xe obtenu

# List.map fact [1;2;3;4;5;6;7;8;9;10] ;;

- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]

Exercice 1. Dénir de façon analogue les fonctions 1. bonacci

2. pgcd de deux entiers 3. longueur d'une liste

4. concaténation de deux listes.

Références

Documents relatifs

Ce théorème rappelle le théorème des bornes pour des fonctions de R dans R : &#34;Toute fonction d’une variable continue sur un segment est bornée et atteint ses bornes&#34;..

= i ; le domaine fondamental Fi est alors l'ensemble des points [ Re(z) [ ^ ^» z z ^ i, et possède un sommet parabolique ; d'autre part, deux fonctions invariantes par G ne sont

Mais de même que la concordance entre les signaux reçus par deux voies sensorielles différentes (e.g. le toucher et la vue ; ou le toucher par la main et le lait dans la

En particulier, la donnée d’un faisceau inversible sur 91 équivaut à celle d’un faisceau inverisble sur Hg muni d’une.. action de F’ compatible à son action sur

Nous pouvons considérer cette fonction G elle-même - sa forme pour ainsi dire - comme la valeur (valeur de fonction) d’une nouvelle fonction f telle que G = fx. D’où

Bien plus, au point de vue logique, ce sont ces fonctions étranges qui sont les plus générales, celles qu’on rencontre sans les avoir cherchées n’apparaissent plus que comme un

Le triangle ABC et le triangle ACD sont tous deux des triangles isocèles dont les longueurs des côtés sont dans le rapport du nombre d'or : ce sont deux triangles d'or. Ce qui

Comme d’habitude, une somme de fonctions continues est continue ; un quotient de fonctions continues dont le dénominateur ne s’annule pas est continue ; une composée de