TD n°1 : Types, fonctions et récursivité

Exercice 1 : Portée statique en Caml

In [1]:
let a = 2;;
Out[1]:
val a : int = 2
In [2]:
let f x = a * x;;
Out[2]:
val f : int -> int = <fun>

a vaut $2$ et c'est ceci qui est conservé pour toujours.

In [3]:
let a = 3 in f 1;;
File "[3]", line 1, characters 4-5:
Warning 26: unused variable a.
Out[3]:
- : int = 2

La liaison locale, qui masque la liaison globale, n'est pas utilisée.

In [4]:
let a = 3;;
Out[4]:
val a : int = 3

C'est une nouvelle liaison qui masque l'ancienne, mais ne change rien à f.

In [5]:
f 1;;
Out[5]:
- : int = 2

En programmation fonctionnelle (pure), le résultat d'une fonction est déterministe et ne peut pas dépendre de son contexte d'application. Nous verrons que ce n'est cependant pas le cas en Caml.

Il faut retenir que c'est bien la valeur de a et non l'identifiant "a" qui est utilisée lors de la définition de la fonction. Ceci est heureux, sinon il faudrait proscrire tous les noms de variables qui peuvent être utilisés dans des fonctions, sous peine de changer le comportement des fonctions sans même s'en rendre compte !

Exercice 2

In [6]:
let f1 n m = if n = m then n else m;;
Out[6]:
val f1 : 'a -> 'a -> 'a = <fun>
In [7]:
let f2 x y = let z = x + 1 in y || z > 10;;
Out[7]:
val f2 : int -> bool -> bool = <fun>
In [8]:
let f3 x y = x y;;
Out[8]:
val f3 : ('a -> 'b) -> 'a -> 'b = <fun>

f3 est l'identité fonctionnelle, c'est-à-dire l'identité, mais qui ne peut s'appliquer que sur des fonctions. C'est aussi la fonctionnelle "application" qui prend en entrée une fonction f et un élément x et qui renvoie l'évaluation (au sens mathématique) de f en x.

In [9]:
let f4 x y = x (x y);;
Out[9]:
val f4 : ('a -> 'a) -> 'a -> 'a = <fun>

f4 est la fonction carré (au sens de la composition) : $f \rightarrow f \circ f$

In [10]:
let f5 x y = x (y x);;
Out[10]:
val f5 : ('a -> 'b) -> (('a -> 'b) -> 'a) -> 'b = <fun>
In [11]:
let f6 x y z = (x y) z;;
Out[11]:
val f6 : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c = <fun>

C'est encore l'identité, mais pour des fonctions avec trois flèches (au moins) et c'est aussi la fonction "application" d'une fonction à deux arguments (curryfiée) sur ses deux arguments.

In [12]:
let f7 (x, y, z) = function t -> (t, y, z);;
Out[12]:
val f7 : 'a * 'b * 'c -> 'd -> 'd * 'b * 'c = <fun>
In [13]:
let f8 f g x = f x + g x;;
Out[13]:
val f8 : ('a -> int) -> ('a -> int) -> 'a -> int = <fun>

f8 est la fonction qui renvoie la somme de deux fonctions à valeurs entières.

In [14]:
let f9 f g x = f (g x);;
Out[14]:
val f9 : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>

f9 est la composition de fonctions que nous avons vue en cours : $(f, g) \rightarrow f \circ g$

Exercice 3

In [15]:
let max3 x y z = (max max x) y z;;
Out[15]:
val max3 : ('a -> 'a -> 'a) -> 'a -> 'a -> 'a = <fun>

Observer bien le type de la fonction. Il faut que le premier argument soit du même type que max.

In [16]:
max3 55 0 42;;
Out[16]:
File "[16]", line 1, characters 5-7:
Error: This expression has type int but an expression was expected of type
         'a -> 'a -> 'a
Characters 5-7:
  max3 55 0 42;;
       ^^

Il faut donc lui fournir une fonction de ce même type :

In [17]:
max3 min 0 42;;
Out[17]:
Exception: Invalid_argument "compare: functional value".

Mais on ne peut pas comparer (en général) les valeurs fonctionnelles, Caml ne sait pas comparer des fonctions (différentes), la fonction max3 est inutilisable sauf éventuellement avec max3 max y z, ce qui ne présente que peu d'intérêt.

Bien entendu, ce que l'on voulait était :

In [18]:
let max3 x y z = max (max x y) z;;
Out[18]:
val max3 : 'a -> 'a -> 'a -> 'a = <fun>
In [19]:
max3 55 0 42;;
Out[19]:
- : int = 55

Exercice 4

In [20]:
let add1 (x, y) = x + y;;
Out[20]:
val add1 : int * int -> int = <fun>
In [21]:
let add2 x y = x + y;;
Out[21]:
val add2 : int -> int -> int = <fun>
In [22]:
let uncurry f (x, y) = f x y;;
Out[22]:
val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c = <fun>
In [23]:
let add1_bis = uncurry add2;;
add1_bis (3, 4);;
Out[23]:
val add1_bis : int * int -> int = <fun>
Out[23]:
- : int = 7
In [24]:
let curry f x y = f (x, y);;
Out[24]:
val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c = <fun>
In [25]:
let add2_bis = curry add1;;
Out[25]:
val add2_bis : int -> int -> int = <fun>
In [26]:
add2_bis 41 1;;
Out[26]:
- : int = 42

Exercice 5

In [27]:
let rec composition f = function
  | 0 -> (function x -> x)
  | n -> (function x -> (f ((composition f (n - 1)) x)))
;;
Out[27]:
val composition : ('a -> 'a) -> int -> 'a -> 'a = <fun>

C'est la fonctionnelle qui calcule la composée $n$-ième : $f \rightarrow f^n$.

Exercice 6

In [28]:
let rec nombre_chiffres n =
  if n < 10 then
    1
  else
    1 + nombre_chiffres (n / 10)
;;
Out[28]:
val nombre_chiffres : int -> int = <fun>
In [29]:
nombre_chiffres 5;;
nombre_chiffres 15;;
nombre_chiffres 123456789;;
Out[29]:
- : int = 1
Out[29]:
- : int = 2
Out[29]:
- : int = 9

Exercice 7

In [30]:
let rec mult_russe a b =
  if a = 0 then
    0
  else if a mod 2 = 0 then 
    mult_russe (a / 2) (2 * b)
  else
    b + (mult_russe (a - 1) b)
;;
Out[30]:
val mult_russe : int -> int -> int = <fun>
In [31]:
mult_russe 3 27;;
Out[31]:
- : int = 81

Montrons par récurrence (forte) sur $a \in \mathbb{N}$ que cette fonction se termine et renvoie le produit de $a$ par $b$. Si $a = 0$, il n'y a pas de problème de terminaison et le résultat renvoyé est bien celui escompté. La propriété est donc vraie au rang $0$. Soit $a \geq 1$, tel que la propriété est vraie pour tout $n < a$. Montrons que la propriété est vraie au rang $a$ également. Comme $a \geq 1$, on rentre dans l'un des deux derniers else. Si $a$ est pair, $a / 2$ est un entier naturel strictement inférieur à $a$ et, par hypothèse de récurrence, l'appel récursif sur $(a / 2)$ et $(2 \times b)$ termine et renvoie $(a / 2) \times (2 \times b) = ab$. Sinon $a - 1$ est aussi un entier naturel strictement inférieur à $a$, et par hypothèse de récurrence, l'appel sur $(a - 1)$ et $b$ termine et renvoie $(a - 1)b$. En ajoutant $b$ à ce résultat on trouve encore $ab$. Dans les deux cas la conditionnelle termine et renvoie $ab$ et la propriété est donc vraie au rang $a$. D'après le principe de raisonnement par récurrence la propriété est ainsi vraie pour tout $a \in \mathbb{N}$.

Bien sûr, cette preuve est élémentaire et on ne fera pas toujours les récurrences lorsqu'elles sont aussi simples. Il faudra cependent bien dire que les appels récursifs se font sur une valeur strictement inférieure (ici $a / 2$ et $a - 1$) et atteignent bien l'un des cas de base. Pour la correction, on explicitera au moins très clairement l'hypothèse qui serait démontrée par récurrence.

Voici une autre manière de faire en utilisant une fonction auxiliaire et un accumulateur :

In [32]:
let mult_russe_bis a b =
  (* Diminue a en assurant ab + r constant *)
  let rec aux a b r =
    if a = 0 then
      r
    else if a mod 2 = 0 then
      aux (a / 2) (2 * b) r
    else
      aux (a - 1) b (r + b)
  in
  aux a b 0
;;
Out[32]:
val mult_russe_bis : int -> int -> int = <fun>
In [33]:
mult_russe_bis 3 27;;
Out[33]:
- : int = 81