Commit ee7faca4 authored by Pierre Letouzey's avatar Pierre Letouzey
Browse files

improved and translated preliminary session 1

parent 0a4d927d
Introduction à la Programmation Fonctionnelle en OCaml
======================================================
**M2 LMFI**
## Séance 1 : Démarrage
Le plus simple pour une première découverte : http://try.ocamlpro.com
Sinon vous pouvez directement lancer l'interpréteur `ocaml` dans une console.
Mieux encore, Lancez l'interpréteur OCaml sous l'éditeur `emacs` :
- ouvrez un nouveau fichier `exo.ml` (l'extension `.ml` est nécessaire),
- dans le menu `Tuareg`, dans le sous-menu `Interactive Mode`, choisir `Run OCaml Toplevel`
- confirmez le lancement de `ocaml` par un retour-chariot.
Chaque expression entrée dans la fenêtre de `exo.ml` peut être évaluée en se plaçant sur un caractère quelconque de l'expression, puis en utilisant le raccourci `ctrl-c,ctrl-e`, qui correspond à `Evaluate phrase` dans le sous-menu `Interactive Mode` du menu `Tuareg` d'emacs.
**Nota Bene** : dans emacs, on peut maintenant se passer de l'ancien terminateur `;;`. En contrepartie, *toutes* les phrases doivent commencer par `let`, même les tests. Au lieu de `1+2;;`, on écrira donc maintenant `let _ = 1+2`.
## Coeur fonctionnel d'OCaml
Présentation du lambda-calcul, avec ses trois constructions de base :
- variables
- abstraction de fonction : `λx.t` (correspondant à `fun x -> t` en OCaml)
- application de fonction : `t u` (idem en OCaml)
Règle de calcul : la β-réduction où `(λx.t) u` donne `t{x:=u}` (la définition de la substitution est laissée en exercice).
Exemple de propriété théorique de la réduction du lamda-calcul : la confluence.
Exemple de terme dont la réduction est infinie : `ΔΔ` valant `(λx.(x x))(λx.(x x))`.
Pas d'équivalent direct en OCaml pour des raisons de typage.
Typage simple du lambda-calcul :
- On type les variables en regardant dans un environnement de typage Γ
- Si `Γ+x:τ ⊢ t:σ` alors `Γ ⊢ (λx.t):(τ→σ)`
- Si `Γ ⊢ t:(τ→σ)` et `Γ ⊢ u:τ` alors `Γ ⊢ (t u) : σ`
Lien avec la logique minimale (gérant uniquement l'implication) : il suffit de ne garder que les types dans les règles précédentes.
C'est (le début de) l'isomorphisme de Curry-Howard.
Le typage d'OCaml est une évolution de ces règles de départ, avec en plus une notion de variables de type (`'a` dans la syntaxe OCaml).
Propriétés essentielles du lambda-calcul simplement typé :
- Préservation du typage lors de la réduction
- Normalisation forte : un terme bien typé ne peut avoir de réduction infinie.
Ceci combiné à la confluence signifie que la réduction d'un terme `t` finit toujours sur une unique forme normale `t₀` de `t`.
OCaml satisfait la première propriété (préservation du typage lors du calcul), ce qui est essentiel pour garantir toute une famille de plantage lors du calcul (cf. les "segfaults"). Par contre un système sans calcul infini est très restrictif, OCaml incorpore une notion de récursion générale, cf plus bas.
## Premières extensions OCaml
le `let` (global) et le `let ... in`.
Exemples:
```ocaml
let identity = fun x -> x
(* ou de maniere equivalente: *)
let identity x = x
(* projections, pouvant servir de pseudo-booléens *)
let proj1 x y = x
let proj2 x y = y
let pseudoif b x y = b x y
```
#### Exercice : Mécanisme de liaison en OCaml
Prévoir le résultat fourni par l'interpréteur OCaml après chacune des commandes suivantes :
```ocaml
let x = 2
```
```ocaml
let x = 3 in
let y = x + 1 in
x + y
```
```ocaml
let x = 3
and y = x + 1 in
x + y
```
Question: Pourquoi la deuxième et la troisième commande ne fournissent-elles pas le même résultat ?
Considérons maintenant les phrases suivantes :
```ocaml
let x = 3
let f y = y + x
let _ = f 2
let x = 0
let _ = f 2
```
Question: Quels sont les résultats des appels de fonctions successifs `f 2` ?
#### Exercice : Placement des parenthèses
Question: Ajouter les parenthèses nécessaires pour que le code ci-dessous compile:
```ocaml
let somme x y = x + y
let _ = somme somme somme 2 3 4 somme 2 somme 3 4
```
## Premiers types de données : Booléens et Entiers
OCaml fournit le type `bool` et ses constantes `true` et `false`.
On dispose d'une construction `if ... then ... else ...`.
Quelques opérations booléennes :
- la negation : `not`
- le "et" logique : `&&`
- le "ou" logique : `||`
Remarque : l'évaluation d'un `&&` ou d'un `||` suit des règles particulières (dites *paresseuses*), alors que le reste d'OCaml utilise normalement une évaluation *stricte* : calcul de tous les arguments d'une fonction avant de déclencher cet appel de fonction.
On peut obtenir des résultats booléens lors d'un test d'égalité `x = y` ou de différence `x <> y` ou d'ordre `x < y` ou `x <= y`, etc.
OCaml fournit un type `int` des entiers "machines" (donc à capacité bornée, attention aux débordements).
Il existe un type d'entiers non bornés si besoin.
Quelques opérations sur les entiers : addition `+`, multiplication `*`, division entière `/`, modulo `x mod y`.
#### Exercice : Fonctions booléennes
- Écrire une fonction `checktauto : (bool->bool)->bool` qui teste si une fonction booléenne à un argument répond toujours `true`.
- Même chose avec `checktauto2` et `checktauto3` pour des fonctions booléennes à 2 puis 3 arguments. On peut faire ça en énumerant à la main tous les cas, mais il y a évidemment plus malin, par exemple réutiliser `checktauto`.
- En utilisant des conditionnelles, écrivez une fonction `g` qui se comporte comme la fonction `f` ci-dessous. Vérifier ensuite à l'aide de `checktauto3` que `f` et `g` produisent bien toujours le même résultat.
```ocaml
let f x y z = match x, y, z with
| _ , false , true -> 1
| false , true , _ -> 2
| _ , _ , false -> 3
| _ , _ , true -> 4
```
## Recursivité
Le `let rec` permet de réutiliser la fonction qu'on est en train d'écrire !
Attention à ne pas boucler ! Il faut donc prévoir un cas d'arrêt (p.ex. `x=0`) et faire des appels récursifs sur des valeurs décroissantes.
#### Exercice : écrire quelques fonctions récursives classiques
- Factorielle
- Nombres de Fibonacci. Attention, la version basique de cette fonction a un comportement exponentiel (pourquoi?). Comment l'éviter ?
- Pgcd
- Fonction puissance sur les entiers. Comment faire le moins de multiplications possibles ?
## Fonctions de première classse et application partielle
Des fonctions en argument ou en réponse ...
Des arguments qui manquent ...
Exemple : la composition de fonction
```ocaml
let compose f g = fun x -> f (g (x))
```
Ou de manière équivalente:
```ocaml
let compose f g x = f (g (x))
```
Bien noter (et comprendre!) le type inféré par OCaml: `('a->'b)->('c->'a)->'c->'b`
#### Exercice : des fonctions sur les fonctions
Question: écrire une fonction `sigma` telle que `sigma f a b` calcule la somme des valeurs d'une fonction `f` de type `int->int` entre les entiers `a` et `b`. Quel est le type de `sigma` ?
Question: écrire une fonction `iter` telle que `iter f n x` calcule l'itéré n-ieme de `f` au point `x`. Quel est son type ? S'en servir pour proposer un nouveau codage de la fonction puissance sur les entiers.
## Séance 2, Type de données : les paires
Une paire regroupe deux éléments `(a,b)` qui ne sont pas forcément du même type.
Le type de cette paire s'écrit alors `τ * σ` si les types de `a` et `b` sont respectivement `τ` et `σ`.
Il y a également des triplets, quadruplets, n-uplets bâtis sur le même principe, par exemple `(1,2,true)` est un `int * int * bool`.
Pour utiliser une paire, soit on la projette (via les fonctions prédéfinies `fst` et `snd`),
soit on la déconstruit (via une syntaxe telle que `let (x,y) = ... in ...`.
Exemple : nombres de fibonacci successifs
```ocaml
let fibs n =
if n = 0 then (1,1)
else
let (a,b) = fibs (n-1) in
(b, a+b)
let fib n = fst (fibs n)
```
Fonctions curryfiées ou décurrifiées.
Si une fonction OCaml a plusieurs arguments, l'usage est plutôt de mettre ces arguments les uns après les autres, ce qui donne un type de la forme `typ_arg1 -> typ_arg2 -> ... -> typ_res`. On parle de style *à la Curry*, ou *curryfié*.
Mais il est aussi possible de regrouper tous ces arguments dans un nuplet, ce qui donne un type de la forme `typ_arg1 * typ_arg2 * ... * typ_argn -> typ_res`. C'est le style *décurryfié*. Dans cette version, pas d'application partielle possible aisément. Par contre cela peut parfois être commode de traiter tout un groupe d'arguments comme un seul.
#### Exercice : curryfions et décurryfions
Question : écrire des fonctions `curry` et `uncurry` permettant de passer d'une fonction de type `'a*'b -> 'c` à son homologue de type `'a -> 'b -> 'c` et vice-versa.
## Type de données : les listes
Une liste OCaml est un groupement ordonné d'éléments de même type, en nombre non prédéfini.
Exemple: `[1;2;3;4]` est une `int list`.
Les opérations élémentaires de construction de liste sont la liste vide `[]` et le prolongement
`x::l` d'une liste existante `l` par un nouvel élément `x` à gauche.
Notez que la syntaxe `[1;2;3;4]` n'est qu'un raccourci pour `1::(2::(3::(4::[])))`.
Pour analyser une liste, on peut utiliser des fonctions prédéfinies `List.hd` et `List.tl`, mais il est souvent plus élégant et à peine plus long d'utiliser une opération `match...with`.
Les listes (tout comme les paires auparavant, et les arbres ci-dessous) sont des structures *immutables* : une fois une liste constituée, on ne change plus son contenu. Par contre on peut former de nouvelles listes en réutilisant tout ou partie d'anciennes listes.
La tête de la liste (sa gauche) s'accède ou se ralonge en temps constant. Par contre le fond de la liste (sa droite) ne peut s'atteindre qu'en visitant toute la liste, donc en un temps linéaire (proportionnel à la taille de la liste).
#### Exercice : retrouver quelques fonctions classiques sur les listes
- Recoder une fonction longueur sur les listes (déjà fourni par OCaml sous le nom `List.length`).
- Recoder la concaténation de deux listes (déjà fourni par OCaml via la syntaxe `@`).
- Recoder la concaténation renversant la première liste (cf `List.rev_append`).
- Recoder la fonction de renversement d'un liste (ou miroir), cf. `List.rev`. Si votre solution a un temps de calcul quadratique en la taille de la liste, essayez de trouver également une solution linéaire.
- Recoder le filtre d'un liste en fonction d'un test booléen de ses éléments, cf `List.filter`.
- Recoder `List.map`, à savoir le passage d'une fonction `f` sur tous les éléments d'une liste.
A regarder également dans la documentation : les itérateurs `fold_left` et `fold_right`.
Attention à l'usage sur de grosses listes (plus de 30000 éléments), certaines fonctions peuvent échouer avec l'erreur `Stack overflow`. Pour éviter cela, on peut utiliser le style *récursif terminal* (cf p.ex. `rev_append` ci-dessus) ... ou bien utiliser autre chose que des listes (arbres ou tableaux).
## Type de données : les arbres
Pas de type des arbres prédéfinis en OCaml, il y aurait trop de variantes selon les usages voulus. Heureusement, OCaml nous permet d'ajouter facilement nos propres *types algébriques* au système.
Par exemple, avec des éléments de type `'a` comme décoration à chaque noeud binaire:
```ocaml
type 'a arbre =
| Noeud of 'a * 'a arbre * 'a arbre
| Feuille
let monarbre = Noeud (1,Noeud (2,Feuille,Feuille), Feuille)
```
Pour l'analyse, on utilise la construction `match...with` de manière similaire aux listes
(ou le raccourci `function`, qui correspond à un `fun` suivi d'un `match`).
#### Exercice : généralité sur les arbres
- Ecrire des fonctions calculant la taille et la profondeur d'un arbre
- Ecrire une fonction convertissant un arbre en la liste correspondante (parcours infixe)
L'intérêt de ce genre d'arbre vient par exemple quand les données sont rangées par ordre croissant lors d'un parcours infixe, on parle alors d'arbre binaire de recherche (ABR, ou BST en anglais pour Binary Search Tree).
#### Exercice : arbre binaire de recherche
- Ecrire une fonction `search` vérifiant par dichotomie si un élément est dans un arbre que l'on supposera binaire de recherche.
- Ecrire une fonction `is_bst` qui vérifie si un arbre est bien un arbre binaire de recherche. Quelle complexité peut-on obtenir pour cette fonction ?
Si l'arbre est bien plat (c'est à dire complet ou presque), cette recherche est alors en temps
logarithmique en la taille de l'arbre. Il est possible de s'assurer que les arbres restent plats
même lorsqu'on les fait grossir, voir par exemple les arbres Rouges-Noirs ou les arbres AVL.
OCaml fournit par défaut une bibliothèque de fonctions ensemblistes rapides basées sur des arbres, voir le module `Set`.
## Types algébriques d'OCaml : faites vos propres types
Au delà des arbres, on peut continuer à concevoir des types algébriques correspondant à nos besoins. Par exemple, pour un petit langage de calcul formel:
```ocaml
type unop = Sin | Cos
type binop = Plus | Mult
type expr =
| Cst of int
| Var of string (* nom de la variable *)
| Unop of unop * expr
| Binop of binop * expr * expr
(* codage de 3x+sin(y) *)
let exemple = Binop(Plus,Binop(Mult,Cst 3,Var "x"),Unop(Sin,Var "y"))
```
Notez que le passage d'une chaine de caractère comme `"3x+sin(y)"` à la forme arborescente ci-dessus (de type `expr`) peut se faire automatiquement, mais ce n'est pas évident. Il s'agit d'une analyse lexicale et grammaticale, pour laquelle il existe des outils dédiés (cf `ocamllex` et `ocamlyacc`).
On peut alors écrire un simplificateur d'expression, essayant de se débarasser des constantes et autres sous-expressions simplifiables.
Ceci n'est qu'un début, à perfectionner.
#### Exercice : simplification et derivation formelle
- Ecrire une fonction `simpl : expr->expr` appliquant autant que possibles les règles algébriques de base, telles que `0+x = x`.
- Ecrire une fonction `deriv : string->expr->expr` calculant une dérivée formelle. Le premier argument est le nom de la variable par laquelle on dérive.
## OCaml c'est aussi ...
OCaml est un langage généraliste, donc on y trouve bien d'autres choses
que les éléments présentés lors de ce rapide survol:
- de la programmation impérative (`for`, `while`, références, tableau, ...)
- des primitives d'interaction avec le reste du monde (I/O, fichiers, système, réseau, ...)
- des exceptions (programmables)
- un système de modules
- de la programmation objet
- ...
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment