INFO421 : Programmation fonctionnelle

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche

Ce wiki est un complément de cours pour le cours « info-421 : programmation fonctionnelle ». La participation au wiki est fortement encouragée, et deviendra peut-être obligatoire...

Pour pouvoir modifier les pages, inscrivez-vous (lien en haut à droite) pour obtenir un login et mot de passe. (Utilisez votre vrai nom...)

Je vous conseille d'aller voir ce guide pour vous familiariser avec les wikis.


Exercice : si vous n'en avez pas, créez-vous un compte et essayez de modifier cette page (correction de fôtes d'aurtograffe, rajout de détails, mise en page, ...)

Vous pouvez aussi utiliser la page de discussion pour ... discuter. (Ou poser des questions, faire des commentaires etc.)


Détails techniques

Compléments de cours, TD et TP

Installer Ocaml

Si vous voulez installer OCaml sur votre ordinateur :

  • Sous Linux : c'est la solution idéale. Il existe probablement des paquets pour votre distribution. Pour Ubuntu, pour avoir un environnement similaire à ce que vous aurez dans les salles informatiques, installez les paquets ocaml, ocaml-core, ocaml-mode, tuareg-mode et emacs.
  • Sous MacOS : il semblerait qu'il y ait des paquets MacPorts pour ocaml, emacs et tuareg-mode.el.
  • Sous Windows : je vous renvoie au tutoriel de Jean-Paul Roy : Installation de OCaml (sur Windows XP). Je n'ai malheureusement (??) pas accès à une machine avec Windows (98/2000/XP/Vista/7), je ne pourrais donc pas beaucoup vous aider.

Contactez moi si vous avez des problèmes.

Pour les exemples simples, vous pouvez aussi utiliser OCaml en ligne

Références supplémentaires

Cours

TD et TP

Introduction

Pour paraphraser un collègue dont je ne retrouve pas le nom :

Attention : il ne s'agit pas d'un cours de programmation fonctionelle

Il s'agit plutôt d'un cours de programmation fonctionnelle...

Petit historique censuré

Je ne parlerais pas des langages pré-historiques (cartes perforées, λ-calcul, machines de Turing, ...)

D'après ce site, qui recense la plupart des langages de programmation, il y aurait plus de 2500 langages ! Voici donc une petite liste de langages importants :

  • années 40 : langages d'assemblage (assembleurs). Aussi vieux que les ordinateurs eux-mêmes. Chaque langage d'assemblage est spécifique à une famille de processeurs, ce qui rend les programmes difficiles à porter. (Càd à modifier pour les faire marcher sur d'autres ordinateurs.)
  • FORTRAN (1957, toujours utilisé par les numériciens et physiciens) et COBOL (1960, toujours utilisé en gestion). Ces langages ont connus des évolutions mais restent archaïques par leur conception.
  • LISP : inventé par John McCarthy en 1958. C'est le premier langage fonctionnel. Toujours utilisé (sous différentes formes), en particulier en intelligence artificielle. Ce langage est basé directement sur le λ-calcul de Church.
  • ALGOL (1958, a inspiré de nombreux langages depuis : C, pascal, ...) Le but était de réparer certains défauts des langages de type FORTRAN. (Programmation structurée, blocs, ...)
  • Pascal (1975).
  • C (1972). Toujours très utilisé, sous différentes variantes (notamment C++).
  • Prolog (1972) : programmation logique, paradigme nouveau de programmation. Toujours utilisé par une petite communauté.
  • ML (fin des années 1970 ?), qui ajoute une notion de type que LISP n'avait pas.
  • Smalltalk (fin des année 1983), début de la programmation objet.
  • 1983 : ADA.
  • année '80 : Caml (1987), puis CamlLight(1990), puis OCaml(1996), développé

à l'INRIA. (Dernière version en octobre 2012.)

  • années '90 : Python (version 1.0 en 1994).
  • années '90 : PHP (version 1.0 en 1995).
  • années '90 : Java (version 1.0 en 1996).

Je vous conseille d'aller voir le graphique suivant : Computer Languages Timeline (ou découpé en pages A4).

Fonctionnel ??

L'adjectif fonctionnel a au moins deux sens :

  1. qui fonctionne, en état de marche,
  2. qui se rapporte aux fonctions.

Les langages fonctionnels sont bien entendus fonctionnels dans le premier sens, mais c'est surtout le second sens qui nous intéresse. Les langages tels que Pascal, Ada ou C sont qualifié, par opposition, d'impératifs.

Un des slogans de la programmation fonctionnelle en général est

Les fonctions sont des valeurs comme les autres

et c'est de là que vient la terminologie... En particulier, il n'y a pas de différence entre les instructions (qui ont un effet) et les expressions (qui ont une valeur). Par exemple, en Python,

x = x+1

ou

if delta < 0:
    print("Il n'y a pas de solution")

sont des instructions : on ne peut pas les mettre dans une variable.

Comme nous le verront, cela a des conséquences sur l'expressivité du langage et la manière de programmer.

Le langage (O)Caml

Le langage OCaml est développé par l'INRIA (Institut national de recherche en informatique et automatique). C'est un successeur de CamlLight.

Le nom Caml est formé des initiales de "Categorical Abstract Machine Language", et le langage lui même appartient à la famille de ML ("Meta Language"). C'est un langage fonctionnel strict (nous verrons ce que cela veut dire), statiquement typé (nous verrons ce que cela veut dire) qui supporte plusieurs styles de programmation :

  • fonctionnel bien sûr,
  • mais aussi impératif,
  • objet également (c'est le « O » de OCaml).

Dans ce cours, nous utiliserons principalement le style fonctionnel, et un peu le style impératif en fin de semestre.

Voici quelques aspects importants du langages que nous essayeront d'aborder pendant le cours :

  • fonctions comme valeurs,
  • types de données algébriques
  • polymorphisme,
  • système d'exceptions,
  • support pour des références et des données mutables (programmation « impure »),
  • système de modules et de foncteurs,
  • ...

La notion de récursivité sera fondamentale pendant toute la durée du cours...

Un aspect intéressant du langage est que c'est :

  • un langage interprété (avec l'interpréteur OCaml),
  • soit un langage compilé en bytecode (code binaire indépendant de l'architecture),
  • soit un langage compilé optimisé en binaire (dépendant de l'architecture).

Autres langages fonctionnels

Il existe de nombreux autres langages fonctionnels. Par exemple :

  • SML (Standard ML) : Wikipedia, autre dialecte de la famille ML.
  • LISP, dont les deux dialectes principaux sont :
  • Haskell : Wikipedia (inspiré en grande partie de Miranda).

Plusieurs langages impératifs intègrent maintenant des aspects propres des langages fonctionnels : Python, Scala, ...

Applications concrètes

Voici quelques exemples de logiciels développés en OCaml :

La viabilité du paradigme fonctionnel se retrouve également dans le langage Erlang (Wikipedia), un langage fonctionnel développé par Ericsson pour la programmation concurrente de systèmes temps réels.

Objectifs du cours

  1. être capable de définir des fonctions récursives, et comprendre ce qu'elles font
  2. comprendre le typage, les types algébriques et le polymorphisme à la ML
  3. pouvoir définir des fonction d'ordre supérieur pour modulariser votre code
  4. être capable de décomposer un problème
  5. commencer à réfléchir à la complexité de vos programmes

Premiers pas en Caml

Un des slogans de la programmation fonctionnelle est tout est une valeur, ou plus précisément,

Toute expression a une valeur.

Cette idée n'est pas présente en Python où l'on distingue les expressions ("3*x+f(2)" par exemple) et les instructions ("if n>22: a=12 else: a=13" par exemple). En Python, les instructions sont en général séparées par des retours à la ligne et sont exécutées en séquence. Les expressions sont juste calculées, et ne peuvent pas être séquentialisées.

Dans un langage purement fonctionnel, le ";" n'existe pas, et il n'y a que des valeurs...

Les valeurs sont formées en utilisant :

  • les fonctions du langage (addition, multiplication, opérateurs logiques),
  • des constructions du langage (if ... then ... else ... par exemple),
  • les relations mathématiques (égalité, plus grand), ... qui sont juste des fonctions renvoyant des booléens,
  • les constantes du langage,
  • les variables de l'environnement,
  • les valeurs (en particulier les fonctions) définies par le programmeur.

Bien entendu, comme Caml est typé, il faut respecter les types.

Les types atomiques

Caml (et les autres langages fonctionnels typés) possèdent plusieurs types de base tels que :

  • les booléens : type bool (valeur true et false) et les opérations not, && (et) et || (ou).
  • Les entiers : type int pour les entiers signés compris entre et . (Les entiers Caml sont stockés sur 31 bits...) Les opérations associés sont +, -, *, / (pour la division entière) et mod (pour le modulo).
  • Les flottants : type float pour les réels en virgule flottante. Les opérations usuelles sont +., -., *. et /..
  • Les caractères : type char (notés entre guillemets simples).
  • Le type unité : type unit, dont l'unique valeur est (). (Nous verrons que ce type a une utilité...)

Même s'il ne s'agit pas vraiment d'un type atomique, nous pouvons également ajouter le type des chaînes de caractères : type string (notées entre guillemets doubles).

<source lang="ocaml"> (* exemples ... *) </source>

Remarques : les opérations booléennes ("&&" pour le « et » et "||" pour le « ou ») fonctionnent de à gauche à droite, et l'argument de droite n'est évalué que si c'est nécessaire. Par exemple true || (0 = 1/0) donne la valeur true, alors que que false || (0=1/0) lève l'exception Division_by_zero.

définitions et environnement

Comme tous les langages de programmation, Caml garde en mémoire une liste de définitions. Chacune de ces définitions met en relation un nom ("x", "n", "length", ...) et une valeur ("3.7", "42", "fun s -> ...").

Lorsque l'on lance Caml, l'environnement contient déjà de nombreuse fonctions prédéfinies : max, min, mod, +, float_of_int, ...

Certaines des variables de l'environnement correspondent à des paramètres de fonction en cours de définition. Ces variables n'ont pas de valeur, mais seulement un type.

Environnement

  • Liste de paires nom / valeur,
  • une nouvelle definition remplace la précédente mais ne l'efface pas, (on ne met pas une valeur à jours)

Définitions

L'utilisateur peut rajouter des définitions dans l'environnement grâce au mot clé let

  • let nom = expr permet de rajouter une définition simple dans l'environnement. Exemple : let n = 42
  • let nom1 = expr1 and nom2 = expr2 permet de rajouter plusieurs définitions simultanément. Exemple : let a=1 and b=2
  • let rec nom1 = expr1 and nom2 = expr2 permet de rajouter des définitions mutuellement récursives.

On peut toujours annoter la définition de types de données. Ceci évite à Caml d'avoir à deviner le type que l'on souhaite et permet de préciser la fonction : <source lang="ocaml">

let n:int = 42
let rec f (n:int) : string = if (n<1) then "" else "*" ^ (f (n-1))

</source>

Si une définition utilise le même nom qu'une définition précédente, la nouvelle définition écrase l'ancienne. Par exemple : <source lang="ocaml">

# let n=42 ;;
val n : int = 42
# let n=3.1415 ;;
val n : float = 3.1415
# n ;;
  • : float = 3.1415

</source>

Définitions locales

Il est possible de modifier l'environnement de manière temporaire (locale). On utilise alors let nom = expr1 in expr2. Ceci ajoute la définition nom = expr1 dans l'environnement, mais seulement pour l'évaluation de l'expression expr2. <source lang="ocaml">

  1. let x =
 let y = 42 in
   y/2 ;;

val x : int = 21

  1. x ;;
  • : int = 21
  1. y ;;

Unbound value y </source>

Les fonctions

Caml utilise la notation mathématique pour écrire les types des fonctions. Si f est une fonction avec un seul argument de type string et dont la valeur de retour est de type int (la fonction length par exemple), le type de f est noté string -> int. Dans l'interprète Caml : <source lang="ocaml">

# String.length ;;
  • : string -> int = <fun>

</source> Nous indique que la fonction length est bien de type string->int.

Une fonction est définie comme une variable avec un let et en utilisant le mot clé fun : <source lang="ocaml">

  1. let f = fun n -> n+1;;

val f : int -> int = <fun>

  1. let racine = fun n -> int_of_float (sqrt (float_of_int n));;

val racine : int -> int = <fun> </source>

Une fonction avec plusieurs argument est définie de la même manière avec <source lang="ocaml">

  1. let moyenne3 = fun x y z -> (x +. y +. z) /. 3. ;;

val moyenne3 : float -> float -> float -> float = <fun> </source>

Le type d'une fonction à plusieurs argument est noté en mettant plusieurs types à gauche de la flèche ->, comme ci dessus : type_arg1 -> type_arg2 -> type_arg3 -> type_resultat.

Remarques :

  • notez l'absence de mot clé return et l'absence de virgules pour séparer les différents arguments !
  • Le mot clé fun permet de définir des fonctions sans leur donner de nom, ce qui n'est pas possible dans les langages comme C ou Java...

On peut également utiliser un raccourci pour définir une fonction sans utiliser le mot clé fun : il suffit de mettre les arguments de la fonction avant le signe égal : <source lang="ocaml">

  1. let f n = n+1;;

val f : int -> int = <fun>

  1. let racine n = int_of_float (sqrt (float_of_int n));;

val racine : int -> int = <fun>

  1. let moyenne3 x y z = (x +. y +. z) /. 3. ;;

val moyenne3 : float -> float -> float -> float = <fun> </source>

Pour appliquer une fonction à des arguments, on écrit simplement <source lang="ocaml"> moyenne3 13.5 17.0 -0.333 </source> Il n'y a ni parenthèse, ni virgule. Les parenthèses peuvent cependant être nécessaires pour différencier <source lang="ocaml"> moyenne3 13.5 17.0 (-0.333 *. 6.) </source> et <source lang="ocaml"> (moyenne3 13.5 17.0 -0.333) *. 6. </source> ou pour que Caml lise correctement <source lang="ocaml"> moyenne3 13.5 (17.0 -. 12.0) -0.333 </source>

Fonctions récursives

Si on essaie de définir une fonction récursivement : <source lang="ocaml"> let f n = if (n<1) then 0 else n + f (n-1) </source> Caml répond <source lang="ocaml"> Error: Unbound value f </source> car f n'est pas présente dans l'environnement.

Pour rajouter f comme variable active (sans valeur) et pouvoir faire une définition récursive, il faut utiliser le mot clé rec : <source lang="ocaml">

  1. let rec f n = if (n<1) then 0 else n + f (n-1);;

val f : int -> int = <fun> </source>

Les définitions locales peuvent elles aussi être récursives (let rec ... in ...) ou mutuellement récursives (let rec ... and ... in ...). Elles acceptent également les annotations de type...

fonctions d'ordre supérieur

TODO

Les listes

Les types algébrique

Les structures

La récursivité terminale

Les exceptions

Aspects impératifs dans Caml

Les modules