Connexion élèves

Les langages fonctionnels

Mais plutôt que de "détourner" Python pour faire de la programmation fonctionnelle, ne serait-il pas plus simple d'utiliser un langage fait pour cela ?

Vous avez déjà eu un aperçu de Lisp, qui est un langage fonctionnel.

Le langage Haskel est exclusivement fonctionnel, il interdit l'affectation des variables etc...et est donc très difficile de coder ainsi.

Le langage Ocaml fait partie de la famille des langages ml, c'est aussi celui qui a été choisi pour l'enseignement de l'informatique en CPGE.
Il est "orienté programmation fonctionnelle" mais autorise quelques incartades au paradigme.
Nous allons (une fois n'est pas coutume...) découvrir un nouveau langage de programmation et tenter de programmer quelques fonctions simples en respectant le paradigme fonctionnel (autant que possible).
Cela va être perturbant, car nous allons sortir de nos habitudes, à la fois pour la syntaxe et pour la réflexion algorithmique.

OCaml (pour Objective Caml) est un langage de programmation fonctionnel de la famille ML (pour MetaLanguage ; Standard ML est l’autre langage fonctionnel le plus utilisé avec F#), dont les racines sont tirées de Lisp (1958).
Il est développé en France par l’INRIA (Institut National de Recherche en Informatique et en Automatique).

Ce qui va suivre est, en partie, extrait du cours de M. Patrice Poincloux (option informatique en CPGE au lycée Vaugelas)

Les bases d'OCaml

Comment exécuter un programme en Ocaml ?

Dans un terminal

OCaml est installé sur les PC du lycée; si vous lancez OCaml dans le menu, vous obtenez une fenêtre de shell ( = interpréteur ) comme ci-dessous :

Ocaml

Le prompt est représenté par un #, et non >> comme en Python.
Nous pouvons donc déjà tester ici quelques "commandes" OCaml.

Tapez le code ci-dessous :


let f x = 2*x + 1;; 				
			

Même si vous ne connaissez pas ce langage, votre culture informatique vous permet de repérer que nous somme en train de définir une fonction.
Si vous validez cette ligne, des informations apparaissent, à vous de les décoder...

Nous allons maintenant utiliser cette fonction. Pour cela tapez puis validez :


f 3;;
			

Le résultat proposé semble assez clair...

On peut remarquer qu'ici les ;; permettent d'envoyer le code à l'exécution. Le résultat est affiché (effets de bord !) et le type de l’expression évaluée est affiché.

Pour quitter la console, on peut au choix taper #exit 0;; ou #quit;;, le dièse fait ici partie de la commande (Ctrl D fonctionne également).

Dans un script

OCaml est un langage qu'il est possible de compiler, c'est un de ses grands intérêts.

Pour travailler avec un script, nous n'allons pas mettre en place un environnement de développement spécifique, nous allons travailler à l'ancienne.
Pour cela vous aurez besoin de :

Pour plus de clarté nous allons travailler sur un exemple :

Voici ce que vous devez faire pour exécuter un script :

  • Saisir le code ci-dessous dans Bluefish :
    		
    let x = 2.0
      let y =
        let x2 = x *. x in
        let x4 = x2 *. x2 in
        x4 *. x4
    let () =
      Printf.printf "x = %f ; y = %f\n" x y
    				

    Puis enregistrer le fichier sous le nom test1.ml.

  • Se placer dans le répertoire de votre script depuis la fenêtre de shell système.
  • Compiler votre script en tapant le code suivant :
    
    ocamlc -o premiertest test1.ml			
    				

    ( ainsi OCaml va transformer votre script test1.ml en un fichier exécutable premiertest).

  • Exécuter votre fichier avec la commande :
    
    ./premiertest			
    				

En cas d'erreur d'exécution du script, un message approprié apparaîtra dans le shell système :

Ocaml

En ligne

Les éditeurs de cette page sont paramétrés pour interpréter directement du code OCaml :

let x = 2.0 let y = let x2 = x *. x in let x4 = x2 *. x2 in x4 *. x4 let () = Printf.printf "x = %f ; y = %f\n" x y;;

Syntaxe de base

Nous allons d'abord essayer de découvrir la base de la syntaxe en OCaml, en utilisant la ligne de commande.

Liaison identifiant-valeur, opérations de base et types

Liaison

Schématiquement, un programme OCaml est une suite d’instructions globales let dont le rôle est de lier un identifiant à une valeur.
Quand on écrit :

		
let x = 3 ;;	
			

→ on lie la valeur 3 à l'identifiant x. On ne pourra (devra) plus modifier la valeur de x ensuite.

On peut traduire let par "soit" comme on dirait en mathématiques "soit x égale 3".

Les noms de valeurs doivent commencer par une lettre minuscule ou par _.
Ils peuvent contenir des chiffres, des majuscules, l’underscore _ et l’apostrophe (ce qui permet des noms comme x' ou x'' ).

Les opérateurs

Les opérateurs classiques +, -, *, / ne sont pas les mêmes pour les entiers et les flottants :

Vous pouvez ainsi tester :

		
2 + 3;;
			

puis :

		
2.2 + 3.3;;
			

et enfin :

		
2.2 +. 3.3;;
			

Les opérateurs booléens s'écrivent & ou &&, or ou ||, not.
On préfère généralement utiliser && et || pour ne pas utiliser and comme opérateur booléen.

and n'est pas un opérateur booléen en OCaml.
Il permet de définir des constantes locales de même niveau simultanément.

		
let x = 1 and y = 2 ;;
				
Les types

Pour ce qui est des types, OCaml dispose de tous les types classiques :

Il existe aussi des types construits :

Mais nous ne les utiliserons pas dans cette petite initiation à OCaml.

Typage fort et inférence de type
  • Comme Python, OCaml est doté d’un algorithme d’inférence de type, qui permet au langage d’assigner automatiquement le bon type aux expressions sans que le programmeur ait besoin de le préciser. Quand on déclare un nom, on ne précise donc pas le type.
  • Contrairement à Python, OCaml est un langage fortement typé : on n’ajoute pas un entier à un flottant.

Pour appliquer tout cela, deux petits exercices :

Le nombre pi n'est pas implémenté dans OCaml; pour l'utiliser on se servira de la fonction tangente.
En math, vous savez que tan(π /4) = 1 . On utilisera donc la valeur arctan(1) qui s'écrit en Ocaml atan 1.

  1. Calculer le périmètre d'un cercle de rayon 0,12 m.
  2. Calculer la surface d'un disque de rayon 0,12 m.

SOLUTION

Liaison locale

Comme vous l'avez vu, il est possible de lier une valeur et un nom. La liaison classique est alors globale pour tout le programme ou la session de terminal.
Mais il est aussi possible de déclarer un nom de façon locale, pour simplifier l'écriture d'un calcul par exemple.
La structure de la liaison est alors la suivante :

		
let ..... in .....
			

La portée de la liaison locale se limite à la série d'instructions suivant le in.

Pour mieux comprendre ce concept, il faut bien sur l'expérimenter...
Par exemple :

		
let x = 3 in x + 1;;
			

→ liera x avec la valeur 4.

On peut vérifier que la portée de la variable locale y est limitée dans l'exemple suivant :

let x = 7;; (* portée globale *) let y = 3 in x+y;; (* portée locale au calcul de la somme *) print_int x; print_newline ();; (* print_int y;; (* erreur ! *) *)

Essayez de prévoir la valeur renvoyée par les liaisons suivantes :

		
(* exemple 1*)
let x = 3 in x * x;;
			

(* exemple 2*)		
let x = 7;;
let y = 5 in x * y;;
			

(* exemple 3*)					
let x = 3 in let x = x + 1 in x + 3;;
			

(* exemple 4*)					
let a=
	let pi = 4. *. atan 1. in
	cos(pi /.3);;
			
				
(* exemple 5*)	
let a,b = true, false in a && b;;
			

SOLUTION

Les fonctions en Ocaml

Fonctions classiques

Avec ces quelques notions, nous commençons à pouvoir coder en OCaml. Mais si nous utilisons le paradigme fonctionnel, il serait cohérent de savoir coder une fonction.
La définition de fonction est assez simple en Ocaml :

Par exemple :


let carreEntier x = x * x ;;		
			

L'appel de fonction se fait également sans parenthèse:


carreEntier 3 ;;		
			

Une fonction peut également avoir plusieurs variables :


let somme a b = a + b ;;		
			

Le résultats renvoyé par OCaml est alors :


val somme : int -> int -> int = ‹fun›
			

→ on retrouve donc bien la trace et les types associés aux paramètres et à la fonction elle-même. On peut éventuellement remarquer que l’enchaînement des types est étonnant. En fait, OCaml ne gère que des fonctions d'une seule variable et travaille avec des fonction composées, ici f(a(b)).

Contrairement à Python, pas de return en OCaml : la fonction renvoie la dernière expression évaluée (comme Lisp...)

On peut également coder des fonctions sans paramètre, notamment pour isoler les effets de bords (affichages, saisie) en dehors du code principal, et garder ainsi autant que possible les avantages de la programmation fonctionnelle :


let affiche () = print_string "ceci est un effet de bord";;
			

→ on remarque ici l’apparition de parenthèses : elle ne signifient pas qu'il n'y a pas de paramètre entre ces parenthèses.
() est bien ici le seul objet de type unit (il correspond à None en python).
On précise donc que le paramètre de la fonction est None : elle ne renvoie rien.
Pour appeler la fonction, il sera également nécessaire de préciser ce paramètre :


# affiche ();;
ceci est un effet de bord
- : unit = ()
			
A vous de coder quelques fonctions simples.
  1. coder une fonction qui calcule le périmètre d'un cercle.
    let perimetre r =
  2. coder une fonction qui calcule la surface d'un disque.
    let surface r =
  3. coder une fonction qui calcule le volume d'un pavé.
    let volume d l h =
  4. coder une fonction qui calcule la moyenne de trois nombres passés en paramètres.
    let moyenne n1 n2 n3 =

SOLUTION

Fonctions récursives

Bon, tout cela est bien simple, vu votre niveau en programmation, vous aimeriez bien aller un peu plus loin...
Nous allons donc nous attaquer aux fonction récursives. Mais avant cela, vous vous rappelez qu'une fonction récursive, c'est (si on simplifie un peu...):

→ il faut donc nécessairement pouvoir traiter des conditions.

Les conditions

La syntaxe du traitement conditionnel en OCaml est assez simple :

	
if ... then ... else...				
			

Comme dans cette fonction :

let f x = if x = 3 then print_string "bravo c'est 3" else print_string "perdu";;

Nous pouvons constater ici que cette fonction, par l'affichage de chaînes de caractères, entraîne des effets de bord.
Normalement, tous les effets de bords devraient être isolés dans des fonctions "à part".

Les fonction récursives

Les fonction récursives en OCaml sont déclarées différemment avec le mot clef rec :


let rec frecurs x = 
  ...		
			

Le plus simple encore une fois est de consulter un exemple :

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

On reconnaît ici la fonction factorielle. Les sauts de ligne ne sont pas obligatoires mais permettent "d'aérer" le code.

  • Lors de l'appel récursif, on utilise des parenthèses
  • Lors de l'appel principal de la fonction, pas besoin de parenthèses :
    
    fact 5;;		
    				
  • Si vous souhaitez travailler dans un script, vous pouvez appeler la fonction ainsi :
    
    print_int(fact 5);
    print_newline ();;
    			
  1. Codez une fonction récursive calculant la somme des n premiers entiers.
  2. Testez cette fonction pour quelques valeurs.
let rec somme n =
  1. Codez une fonction récursive calculant les termes de la suite de Fibonacci.
  2. Testez cette fonction pour les 10 premiers termes de la suite.
let rec fibo n =

SOLUTION

Quelques exercices

Il y aurait encore beaucoup de choses à dire pour égaler en OCaml vos connaissances en Python.
Mais cela va suffire pour réaliser quelques exercices basiques.

  1. Codez une fonction abs qui renvoie la valeur absolue d'un réel.
  2. Testez cette fonction pour quelques valeurs.
let abs n =
  1. Codez une fonction max qui prend 4 paramètres a, b, c, d et qui renvoie le couple de valeur correspondant à la fraction a/b ou c/d la plus grande.
  2. Testez cette fonction sur plusieurs couples de valeurs.
let max a b c d =
  1. Codez une fonction racine qui prend 3 paramètres a, b, c et qui renvoie les solutions réelles de l'équation a.x²+b.x+x=0.
  2. Testez cette fonction sur plusieurs triplets de valeurs. (par exemple (1,0,-1) doit renvoyer 1 et -1).
let racine a b c =

SOLUTION