Noyau Fonctionnel

publicité
OCaml : un Langage Fonctionnel
Alan Schmitt
26 septembre 2016
Informations Pratiques
▶
Équipe pédagogique
▶
▶
Alan Schmitt (cours, 2 groupes de TP)
Barbara Kordy (2 groupes de TP)
▶
4 cours
▶
9 TP notés
▶
pas d’examen
▶
http://www.irisa.fr/celtique/aschmitt/teaching/
[email protected]
Pourquoi apprendre OCaml ?
Objectifs de ce cours
▶
Comprendre des concepts fondamentaux de la programmation
(types, récursion)
▶
Apprendre à exploiter les atouts de la programmation
fonctionnelle
Pourquoi ?
▶
Pourquoi s’intéresser à ce style de programmation ?
▶
Peut-on résoudre les mêmes problèmes qu’en C ou Java ?
Pourquoi s’intéresser à la programmation fonctionnelle (1)
Approche favorisant des programmes
▶
corrects;
▶
lisibles;
▶
réutilisables ou modifiables.
Un langage de programmation est un outil pointu. Les langages
fonctionnels sont le résultat de nombreuses années de recherche.
Pourquoi s’intéresser à la programmation fonctionnelle (2)
Langages fonctionnels proches des mathématiques. Ce sont des
langages de haut niveau :
▶
permettant de s’abstraire de l’architecture des machine;
▶
donnant des programmes clairs et concis;
▶
favorisant un développement rapide;
▶
fournissant des outils pour une meilleure sûreté (types).
Pourquoi s’intéresser à la programmation fonctionnelle (3)
De nombreux langages intègrent des aspects fonctionnels:
▶
Java
▶
Objective C
▶
JavaScript
▶
Swift
Exemple: gestion de la mémoire
Allouer une liste en C:
struct
struct
if (x1
x1->hd
return
list * x1 = malloc(sizeof(struct list));
list * x2 = malloc(sizeof(struct list));
== NULL || x2 == NULL) return NULL;
= 1; x1->tl = x2; x2->hd = 2; x2->tl = NULL;
x1;
Allouer une liste en OCaml
1 :: 2 :: []
Peut-on tout programmer en OCaml?
Règle 110�:
Cellules
Nouveau centre
Exemple�:
00000001000
00000011000
00000111000
00001101000
00011111000
00110001000
111
0
110
1
101
1
100
0
011
1
010
1
001
1
000
0
Règle 110 en OCaml
type cell = T | F
let rule110 triple = match triple with
| (T,T,T) | (T,F,F) | (F,F,F) -> F
| _ -> T
let map3 f l =
let rec aux curl = match curl with
| a :: b :: c :: tl -> f (a,b,c) :: aux (b :: c :: tl)
| [a;b] -> [f(a,b,F)]
| _ -> assert false
in aux (F :: l)
let init = [F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;T]
let rec loop n cur =
List.iter (fun F -> print_char ' ' | T -> print_char 'X') cur;
print_newline ();
if n < 20 then loop (n+1) (map3 rule110 cur)
let _ = loop 0 init
Influence
Haskell
F#
Scala
Swift
Influence
https://facebook.github.io/reason/
Grands succès
https://ocaml.org/learn/success.html
Apprendre OCaml
http://ocaml.org
Apprendre OCaml : des livres
http://caml.inria.fr/pub/distrib/books/llc.pdf
http://www.pps.jussieu.fr/Livres/ora/DA-OCAML/
http://programmer-avec-ocaml.lri.fr/
Apprendre OCaml : des leçons en ligne
http://try.ocamlpro.com/
Apprendre OCaml : un MOOC
MOOC commencé le 26 septembre, on peut encore s’inscrire
https://www.fun-mooc.fr/courses/parisdiderot/
56002S02/session02/about
Introduction
Un langage de la famille ML
fonctionnel les fonctions sont des valeurs de première classe
typé inférence de types et types polymorphes
let composition f g = fun x -> f (g x)
val composition : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b =
<fun>
filtrage avec déclaration de nouveaux types
type cell = T | F
let rule110 triple = match triple with
| (T,T,T) | (T,F,F) | (F,F,F) -> F
| _ -> T
type cell = T | F
val rule110 : cell * cell * cell -> cell = <fun>
Un langage fonctionnel
▶
en maths:
N→N
n 7→ 2n + 3
▶
en C (nom obligatoire, instruction return):
int f (int x) { return 2*x + 3; }
▶
en OCaml
fun x -> 2*x + 3
Avantages :
▶
tout est expression, assemblées comme des briques Lego
▶
raisonnement local (pas d’effet de bord)
▶
facile de créer des structures de données
▶
le typage capture des erreurs avant l’exécution
Exemple
let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1))
let res = fact 10
val fact : int -> int = <fun>
val res : int = 3628800
Programmes
Un programme est une suite de phrases
let identifiant = expression
comme dans
let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1))
let res = fact 10
Exécuter un programme, c’est exécuter toutes ses phrases dans
l’ordre (pas de main)
Exécuter une phrase, c’est calculer la valeur de expression, et
d’associer le nom identifiant à cette valeur
Deux outils pour d’exécuter les programmes
Interprète
Méthode interactive pour écrire des programmes. OCaml exécute
chaque phrase, et imprime le résultat de son évaluation sous la
forme val identifiant : type = valeur.
let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1))
let res = fact 10
val fact : int -> int = <fun>
val res : int = 3628800
avantages interactif, permet d’évaluer des programmes partiels,
donne les types et valeurs des phrases
inconvénients lent, utilisable que pour le développement du
programme, OCaml doit être installé
Deux outils pour d’exécuter les programmes
Compilateur
Un outil transformant le programme en du code machine
ocamlopt -o fact fact.ml
./fact
avantages rapide, pas besoin d’avoir à installer OCaml
inconvénients n’imprime rien, sauf si instruction spécifique
let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1))
let res = fact 10
let () = Printf.printf "résultat : %d\n" res
ocamlopt -o fact2 fact2.ml && ./fact2
résultat : 3628800
Les définitions
En Caml une définition consiste à donner un nom à une valeur
Syntaxe
let identifiant = expression
Exemple
let x = 3 + 5
val x : int = 8
Définitions récursives
Si expression mentionne nom (fonctions récursives), on utilise
let rec nom = expression
Portée des noms
(* À ce niveau aucun nom n’est défini *)
let x =
(* x n’est toujours pas défini *)
1 + 2
(* x est maintenant défini et vaut 3 *)
let x =
(* x vaut toujours 3 *)
x + 1 (* le x est l’ancien qui vaut 3 *)
(* un nouveau nom x cache l’ancien, et vaut 4 *)
let rec fact =
(* fact est défini, et vaudra le résultat de l’évaluation du reste *)
function n -> if n < 2 then 1 else n * (fact (n - 1))
(* le fact ci-dessus est le même que celui qu’on défini *)
Portée des noms
(* À ce niveau aucun nom n’est défini *)
let x =
(* x n’est toujours pas défini *)
1 + 2
(* x est maintenant défini et vaut 3 *)
let x =
(* x vaut toujours 3 *)
x + 1 (* le x est l’ancien qui vaut 3 *)
(* un nouveau nom x cache l’ancien, et vaut 4 *)
let rec fact =
(* fact est défini, et vaudra le résultat de l’évaluation du reste *)
function n -> if n < 2 then 1 else n * (fact (n - 1))
(* le fact ci-dessus est le même que celui qu’on défini *)
Le typage
Le typage est une technique pour détecter automatiquement une
classe d’erreurs de programmation:
let x = 1 + 2 * 3
val x : int = 7
mais
let x = 1 + "toto"
Characters 12-18:
let x = 1 + "toto";;
^^^^^^
Error: This expression has type string
but an expression was expected of type int
Limites du typage
▶
Correct et bien typé:
let rec fact = fun n -> if n < 2 then 1 else fact(n - 1) * n
val fact : int -> int = <fun>
▶
Incorrect et mal typé:
let rec fact = fun n -> if n < 2 then 1 else fact(n - "toto") * n
Characters 54-60:
let rec fact = fun n -> if n < 2 then 1 else fact(n - "toto") * n
;;
^^^^^^
Error: This expression has type string
but an expression was expected of type int
▶
Incorrect mais bien typé:
let rec fact = fun n -> if n < 2 then 1 else fact(n + 1) * n
val fact : int -> int = <fun>
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
les nombres
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
les nombres
les fonctions
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
▶
les nombres
les fonctions
…
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
▶
▶
les nombres
les fonctions
…
les expressions réduites à un nom
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
▶
les nombres
les fonctions
…
▶
les expressions réduites à un nom
▶
les appels de fonction
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
▶
les nombres
les fonctions
…
▶
les expressions réduites à un nom
▶
les appels de fonction
▶
les expressions conditionnelles
Les catégories d’expressions
let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n
▶
les expressions réduites à une valeur
▶
▶
▶
les nombres
les fonctions
…
▶
les expressions réduites à un nom
▶
les appels de fonction
▶
les expressions conditionnelles
▶
les expressions de filtrage
Les valeurs élémentaires
Les valeurs élémentaires : les nombres entiers
▶
valeurs dans [−230 , 230 − 1] ou [−262 , 262 − 1]
▶
type int
▶
opérateurs principaux :
+
▶
-
*
/
mod
exemple :
let _ = 5 / 2
- : int = 2
Les valeurs élémentaires : les nombres flottants
▶
valeurs : 41.5, 24e5
▶
type : float
▶
opérateurs principaux :
+.
▶
*.
/.
**
fonctions principales :
sqrt
▶
-.
log
exp
exemple :
let _ = 5. /. 2.
- : float = 2.5
sin cos asin acos tan atan
Les valeurs élémentaires : les booléens
▶
les 2 valeurs :
true
false
▶
type : bool
▶
opérateurs principaux :
not
▶
&&
||
Attention: && et || sont évalués de gauche à droite
Les valeurs élémentaires : les caractères
▶
valeurs :
'3'
'a'
';'
▶
type : char
▶
opérations : voir cours sur la programmation impérative et
http://caml.inria.fr/pub/docs/manual-ocaml/
libref/Char.html
Les valeurs élémentaires : les chaînes
▶
valeurs :
"Hello World!"
▶
type : string
▶
opérateurs principaux : ^ (concaténation), String.length
▶
http://caml.inria.fr/pub/docs/manual-ocaml/
libref/String.html
Les valeurs élémentaires : l’unité
▶
unique valeur : ()
▶
type : unit
▶
utilisé comme type de retour des fonctions qui ne renvoient
rien
let _ = print_endline
- : string -> unit = <fun>
▶
on peut utiliser « () » à la place de « _ » quand on veut
ignorer le résultat d’une fonction mais indiquer qu’elle ne
retourne rien
let () = print_endline "Bonjour"
Bonjour
Les valeurs élémentaires : les fonctions
▶
valeur de la forme fun variable -> expression
fun x -> 3 * x + 1
- : int -> int = <fun>
▶
type argument -> résultat
▶
opérateur principal, l’application (dénoté par la juxtaposition)
(fun x -> 3 * x + 1) 5
- : int = 16
Ces fonctions sont anonymes. Pour leur donner un nom, on utilise
une définition:
let mafonction = fun x -> 3 * x + 1
val mafonction : int -> int = <fun>
Comment lire le type des fonctions ?
Le parenthésage à droite est implicite dans l’expression du type des
fonctions
int -> int -> int -> int
=
int -> (int -> (int -> int))
Fonction qui prend un entier en argument, qui rend une fonction
qui prend un entier en argument, qui rend une fonction de entier
dans entier.
Fonctions : ordre supérieur
Une fonction est une valeur : elle peut être l’argument ou le
résultat d’autres fonction.
Une fonctionnelle est une fonction acceptant en argument et/ou
rendant en résultat d’autres fonctions.
Fonctionnelles : exercices
Soit la fonction sin : float → float
▶
définir en Caml une fonction g
g : x 7→ sinx x
▶
définir en Caml une fonctionnelle h
h : f 7→ (x 7→ f(x)
x )
Fonctionnelles : solutions
let g = fun x -> (sin x)/.x
val g : float -> float = <fun>
let h = fun f -> (fun x -> (f x)/.x)
val h : (float -> float) -> float -> float = <fun>
let _ = h sin
- : float -> float = <fun>
(c’est la fonction sin(x)/x)
let _ = fun x -> (h sin) x
- : float -> float = <fun>
(c’est évidemment la même fonction)
Conversions
int_of_float
- : float -> int = <fun>
float_of_int
- : int -> float = <fun>
int_of_char
- : char -> int = <fun>
…
Valeurs composées : NUplets
Valeurs: (e1, e2, e3, ...)
Type: (t1 * t2 * t3 * ...)
let _ = (1, 1.2, true)
- : int * float * bool = (1, 1.2, true)
let _ = fst((1,2+3),3)
- : int * int = (1, 5)
Valeurs : Bilan
expression (e)
valeurs atomiques
25
87.62
’f’
true
”caml”
()
valeurs composées
(e1, ..., en)
valeurs fonctionnelles
fun v -> e
type (t)
int
float
char
bool
string
unit
t1 * ... * tn
t1 -> t2
Expressions
Définitions
Définir = donner un nom à une valeur
let x = 5-3
val x : int = 2
let y = true
val y : bool = true
ocaml évalue la partie droite et l’associe au nom, que l’on peut
ensuite utiliser:
let _ = x*x
- : int = 4
Définitions
Attention: x désigne la valeur, pas l’expression
let x = 5-3
val x : int = 2
let y = 2*x
val y : int = 4
let x = 1
val x : int = 1
let _ = y
- : int = 4
Définitions locales
let nom = expr1 in expr2
Sémantique
1. on évalue expr1, qui donne val1
2. on évalue expr2 en ayant associé nom à val1
Exemple
let _ =
let x = (11 + 10) in x + x
- : int = 42
Attention
En OCaml, let est utilisé à la fois pour introduire une phrase et
pour introduire une définition locale
Noms
Un nom est simplement un synonyme pour la valeur associée.
L’évaluer revient à retourner cette valeur. Le type du nom est le
type de la valeur.
let x = 40 + 2
val x : int = 42
let _ = x
- : int = 42
Expressions conditionnelles (1)
if expr1 then expr2 else expr3
Sémantique
1. on évalue expr1, qui doit être de type booléen, en v1
2. selon v1, on évalue expr2 ou expr3, qui sont de même type
Exemples
let _ = (if 3=4 then 2+7 else 7-7*2) + 4
let _ = (if 3=4 then "foo" else 7-7*2) + 4
Characters 21-26:
let _ = (if 3=4 then "foo" else 7-7*2) + 4;;
^^^^^
Error: This expression has type string
but an expression was expected of type int
- : int = -3
Expressions conditionnelles (2)
On peut écrire
if expr1 then expr2
qui est équivalent à
if expr1 then expr2 else ()
=⇒ le type de expr2 doit être compatible avec unit
let _ = if true then "foo"
Characters 21-26:
let _ = if true then "foo";;
^^^^^
Error: This expression has type string
but an expression was expected of type unit
Le filtrage
Expressions de filtrage
match expr with
| motif1 -> expr1
| motif2 -> expr2
...
| motifn -> exprn
Sémantique
On évalue
On évalue
accepte la
Toutes les
expr et sa valeur v est confrontée aux motifs.
ensuite l’expression associée au premier motif qui
valeur v.
branches doivent retourner une valeur du même type.
Exemple
let _ =
let b =
match b
| true
| false
false in
with
-> "foo"
-> "bar"
- : string = "bar"
Filtres de valeurs, filtre universel, combinaison
▶
fitrage de valeur (true, "foo", 3, …): le motif
3
est « rigide », il accepte un seul élément, l’élément 3.
▶
filtre universel (_): le motif
_
est un motif qui accepte tous les éléments
▶
un filtre (m1,m2) accepte les paires (v1,v2) si m1 accepte v1
et m2 accepte v2. Par exemple,
(true,_)
accepte toutes les paires dont le premier élément est true :
(true,true)
(true,55)
(true,(4,5.67))
...
Noms dans les motifs
Un nom dans un motif est un filtre universel qui est associe le nom
à la valeur acceptée dans l’expression associée
let _ =
match (1,2) with
| (x,1) -> x
| (x,2) -> x + 10
| (_,_) -> 0
- : int = 11
let sumpair = fun x ->
match x with
| (x1,x2) -> x1+x2
val sumpair : int * int -> int = <fun>
C’est comme cela que l’on accède aux éléments d’un nuplet
Noms dans les motifs
Attention
let x = 12
val x : int = 12
let egal_12 n = match n with
| x -> true
| _ -> false
Characters 43-44:
| _ -> false;;
^
Warning 11: this match case is unused.
val egal_12 : 'a -> bool = <fun>
let res = egal_12 42
val res : bool = true
Un nom dans un motif n’est jamais remplacé par une valeur qui lui
serait associée
Définitions et motifs
Les définitions globale ou locale sont en fait de la forme
let motif = expression
let motif = expression in expression
On peut donc écrire :
let (x,y) = (1+2, "foo" ^ "bar")
val x : int = 3
val y : string = "foobar"
let () = print_endline "hello" (* filtrage rigide sur () *)
hello
let _ = 12
- : int = 12
Fonctions et motifs
Comme pour les définitions, les fonctions sont de la forme
fun motif -> expression
Exemple
let sumpair = fun (x1,x2) -> x1+x2
val sumpair : int * int -> int = <fun>
Syntaxe des fonctions
let nom = fun x -> ... -> fun z -> expr
peut s’écrire
let nom x ... z
= expr
De la même manière,
let sumpair = fun (x1,x2) -> x1+x2
peut s’écrire
let sumpair (x1,x2) = x1+x2
Fonctions : combien d’arguments ?
▶
fonction à un argument
let carre x = x*.x
val carre : float -> float = <fun>
▶
fonction à plusieurs arguments (?)
let distance (x,y) = sqrt(carre(x)+.carre(y))
val distance : float * float -> float = <fun>
en fait un seul argument: un couple
▶
la version à deux arguments:
let distance x y = sqrt(carre(x)+.carre(y))
val distance : float -> float -> float = <fun>
Fonctions et argument ()
let foo () = print_endline "Hello"
c’est une fonction
val foo : unit -> unit = <fun>
let _ = foo ()
let _ = foo ()
Hello
Hello
Sans (), le comportement est différent
let foo = print_endline "Hello"
Hello
val foo : unit = ()
let _ = foo
let _ = foo
Exhaustivité
Soit la définition :
let trait_dir dir = match dir with
| "nord" -> 0
| "sud"
-> 1
| "est"
-> 2
| "ouest" -> 3
quel est le comportement de Caml ?
Exhaustivité
Soit la définition :
let trait_dir dir = match dir with
| "nord" -> 0
| "sud"
-> 1
| "est"
-> 2
| "ouest" -> 3
quel est le comportement de Caml ?
Characters 20-94:
....................match dir with
| "nord" -> 0
| "sud"
-> 1
| "est"
-> 2
| "ouest" -> 3..
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
""
val trait_dir : string -> int = <fun>
La fonction et logique
et : bool * bool -> bool
x
true
true
false
false
y
true
false
true
false
let et (x,y) = match (x,y) with
| (true,true)
-> true
| (true,false) -> false
| (false,true) -> false
| (false,false) -> false
val et : bool * bool -> bool = <fun>
et (x,y)
true
false
false
false
Exercice de filtrage (1)
Imaginer une définition de la fonction et plus concise
Exercice de filtrage (1)
Imaginer une définition de la fonction et plus concise
let et (x,y) = match (x,y) with
| (true,y) -> if y then true else false
| (false,y) -> false
val et : bool * bool -> bool = <fun>
Exercice de filtrage (1)
Imaginer une définition de la fonction et plus concise
let et (x,y) = match (x,y) with
| (true,y) -> if y then true else false
| (false,y) -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = match (x,y) with
| (true,y) -> y
| (false,y) -> false
val et : bool * bool -> bool = <fun>
Exercice de filtrage (1)
Imaginer une définition de la fonction et plus concise
let et (x,y) = match (x,y) with
| (true,y) -> if y then true else false
| (false,y) -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = match (x,y) with
| (true,y) -> y
| (false,y) -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = match x with
| true -> y
| false -> false
val et : bool * bool -> bool = <fun>
Exercice de filtrage (1)
Imaginer une définition de la fonction et plus concise
let et (x,y) = match (x,y) with
| (true,y) -> if y then true else false
| (false,y) -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = match (x,y) with
| (true,y) -> y
| (false,y) -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = match x with
| true -> y
| false -> false
val et : bool * bool -> bool = <fun>
let et (x,y) = if x then y else false
val et : bool * bool -> bool = <fun>
Restrictions sur les motifs
▶
une même variable ne peut apparaître qu’une seule fois par
motif: le motif doit être linéaire
let diag p = match p with
| (x,x) -> true
| _ -> false
Characters 31-32:
| (x,x) -> true
^
Error: Variable x is bound several times in this matching
let diag p = match p with
| (x,y) -> x=y
val diag : 'a * 'a -> bool = <fun>
▶
les valeurs acceptées par les motifs ne sont pas forcément
disjointes
▶
le premier motif qui accepte la valeur est celui utilisé
Exercice de filtrage (2)
Définir une fonction de type
val table_ou : bool * bool -> (bool * bool) * bool = <fun>
telle que table_ou (a,b) retourne une paire ((a,b),c) où c est
la somme logique de a et b
Exercice de filtrage (2)
Définir une fonction de type
val table_ou : bool * bool -> (bool * bool) * bool = <fun>
telle que table_ou (a,b) retourne une paire ((a,b),c) où c est
la somme logique de a et b
let table_ou (a,b) = match (a,b) with
| (false,false) -> ((false,false),false)
| x
-> (x,true)
ou plus simplement
let table_ou (a,b) = ((a,b), et (a,b))
val table_ou : bool * bool -> (bool * bool) * bool = <fun>
et comme je n’aime pas les répétitions
let table_ou (a,b) = let p = (a,b) in (p, et p)
val table_ou : bool * bool -> (bool * bool) * bool = <fun>
Filtrages particuliers
▶
Combinaison de motifs
let trait_caract c = match c with
| 'a' | 'e' | 'i' | 'o' | 'u' | 'y' -> "voyelle"
| _
-> "pas voyelle"
▶
Filtrage d’intervalle de caractères
let f c = match c with
| 'a' .. 'z' -> "lettre"
| _
-> "pas lettre"
▶
Nommage de la valeur filtrée, <motif> as <nom>
let maj c = match c with
| ('a'..'z' as l) -> char_of_int (int_of_char l - 32)
| x
-> x
Exercice de filtrage (3)
Supposons donnée une fonction:
val f : int -> int * int * int = <fun>
Écrire une fonction de type
val g : int -> bool = <fun>
telle que:
▶
g x = true si le triplet f x contient au moins un zéro,
▶
g x = false sinon.
Exercice de filtrage (3)
Supposons donnée une fonction:
val f : int -> int * int * int = <fun>
Écrire une fonction de type
val g : int -> bool = <fun>
telle que:
▶
g x = true si le triplet f x contient au moins un zéro,
▶
g x = false sinon.
let g x = match f x with
| (0,_,_) -> true
| (_,0,_) -> true
| (_,_,0) -> true
|
_
-> false
Exercice de filtrage (3)
Supposons donnée une fonction:
val f : int -> int * int * int = <fun>
Écrire une fonction de type
val g : int -> bool = <fun>
telle que:
▶
g x = true si le triplet f x contient au moins un zéro,
▶
g x = false sinon.
let g x = match f x with
| (0,_,_) -> true
| (_,0,_) -> true
| (_,_,0) -> true
|
_
-> false
let g x = match f x with
| (0,_,_) | (_,0,_) | (_,_,0) -> true
| _
-> false
Exercice de filtrage (4)
Définir le prédicat doublon qui reçoit une paire en argument et
rend vrai si les deux éléments de la paire sont identiques, faux
sinon.
Exercice de filtrage (4)
Définir le prédicat doublon qui reçoit une paire en argument et
rend vrai si les deux éléments de la paire sont identiques, faux
sinon.
let doublon (x,y) = x=y
val doublon : 'a * 'a -> bool = <fun>
Polymorphisme & Inférence de
types
Le polymorphisme
certaines fonctions peuvent être appliquées à des arguments dont
le type n’est pas tout le temps le même : fonctions génériques ou
polymorphes
let first (x,y) = x
val first : 'a * 'b -> 'a = <fun>
let _ = first (3, "foo")
- : int = 3
let _ = first (true, first)
- : bool = true
Variables de type
les parties génériques du type sont représentées par des variables
de type notées 'a, 'b, …
▶
first:
- : 'a * 'b -> 'a = <fun>
▶
doublon:
- : 'a * 'a -> bool = <fun>
▶
un changement de structure
let chg_struct ((x,y),z) = (y,(x,z))
val chg_struct : ('a * 'b) * 'c -> 'b * ('a * 'c) = <fun>
Exercice : polymorphisme
Définir la fonction identité
val id : 'a -> 'a = <fun>
qui renvoie son argument, et la fonction de composition
val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun>
qui prend en argument une paire de fonction (f,g) et rend la
fonction composée
Exercice : polymorphisme
Définir la fonction identité
val id : 'a -> 'a = <fun>
qui renvoie son argument, et la fonction de composition
val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun>
qui prend en argument une paire de fonction (f,g) et rend la
fonction composée
let id x = x
Exercice : polymorphisme
Définir la fonction identité
val id : 'a -> 'a = <fun>
qui renvoie son argument, et la fonction de composition
val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun>
qui prend en argument une paire de fonction (f,g) et rend la
fonction composée
let id x = x
let comp (f,g) = fun x -> f (g x)
ou
let comp (f,g) x = f (g x)
Inférence de type
OCaml devine les types, et utilise cette information pour compiler
le programme.
let add x y = x + y
val add : int -> int -> int = <fun>
n’est pas compilé comme
let addf x y = x +. y
val addf : float -> float -> float = <fun>
C’est pour cela que l’on distingue + et +..
Récursivité
Factorielle
▶
définition intuitive: fact n = 1 * 2 * … * n
▶
définition mathématique:
▶
fact(n) = 1
si n < 2
fact(n) = n ∗ fact(n − 1)
si n ≥ 2
en OCaml
let rec fact n = if n < 2 then 1 else n * (fact (n-1))
val fact : int -> int = <fun>
let _ = fact 5
- : int = 120
Calcul de factorielle
Pour calculer e1 * e2, on calcul d’abord e1, puis on calcule e2,
puis on multiplie les résultats
fact 5
5 * (fact 4)
5 * (4 * (fact 3))
5 * (4 * (3 * (fact 2)))
5 * (4 * (3 * (2 * (fact 1))))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120
Lors de l’appel récursif à fact, on doit garder de l’information
pour les calculs à faire après
Fonctions récursives
Une définition récursive valide comporte au moins 2 cas :
▶
un ou plusieurs cas de base (où figure un élément particulier)
if n < 2 then 1
▶
un ou plusieurs cas récursifs, utilisant la fonction en cours de
définition
else n * (fact (n-1))
Attention l’appel récursif doit « se rapprocher » du cas de base.
Sinon: risque de boucle infinie
let rec fact = fun n -> if n < 2 then 1 else n * (fact (n+1))
Anatomie d’une fonction récursive
let rec funrec arg =
(* code exécuté avant l’appel récursif *)
Printf.printf "avant, arg = %d\n" arg;
if arg <= 0 then 0 else
(* appel récursif, il faut se souvenir de arg pour après *)
let res = funrec (arg - 1) in
(* retour de l’appel récursif, on peut utiliser res et arg *)
Printf.printf "après, arg = %d, res = %d\n" arg res;
(* valeur finale retournée *)
(res + 10)
let res = funrec 4
avant, arg = 4
avant, arg = 3
avant, arg = 2
avant, arg = 1
avant, arg = 0
après, arg = 1, res
après, arg = 2, res
après, arg = 3, res
après, arg = 4, res
val funrec : int ->
val res : int = 40
= 0
= 10
= 20
= 30
int = <fun>
Graphiquement
Code
Exécution
Avant (arg)
Avant (1)
Récursion
Avant (2)
Après (arg)
Avant (3)
Avant (4)
Après (4)
Après (3)
Après (2)
Après (1)
Récursion terminale
Si on se souvient de trop de choses, on tombe en panne de
mémoire
let rec stupide n = if n <= 0 then 0 else 1 + (stupide (n-1))
let res = stupide 265000
Stack overflow during evaluation (looping recursion?).
Récursion terminale
Si on se souvient de trop de choses, on tombe en panne de
mémoire
let rec stupide n = if n <= 0 then 0 else 1 + (stupide (n-1))
let res = stupide 265000
Stack overflow during evaluation (looping recursion?).
Si on retourne directement le résultat de l’appel récursif, alors
OCaml optimise et ne se souviens plus des arguments
let rec malin n acc =
if n <= 0 then acc else let acc' = acc + 1 in malin (n-1) acc'
let res = malin 265000 0
val malin : int -> int -> int = <fun>
val res : int = 265000
Domaines de récursion
▶
cas classique: la récurrence sur les entiers
▶
▶
▶
listes
▶
▶
▶
cas de base pour 0
valeur en n+1 calculée avec la valeur pour n
cas de base pour la liste vide
valeur pour l’ajout d’un élément calculé avec la valeur pour le
reste de la liste
arbres, etc …
Exemple
U0 = 4
Un = 2Un−1 + 3n
pour n > 0
let rec u x = if x = 0 then 4 else 2 * u (x-1) + 3 * x
let res = u 10
val u : int -> int = <fun>
val res : int = 10204
Version avec récursion terminale
let urt x =
let rec aux acc
if n > x then
else let acc'
aux acc'
in aux 4 1
let res = urt 10
n =
acc
= 2 * acc + 3 * n in
(n+1)
val urt : int -> int = <fun>
val res : int = 10204
Récursivité croisée
let rec nom1 = expr1
and nom2 = expr2
Les expressions expr1 et expr2 peuvent utiliser nom1 et nom2.
let rec pair n = if n = 0 then true else impair (n-1)
and impair n = if n = 0 then false else pair (n-1)
val pair : int -> bool = <fun>
val impair : int -> bool = <fun>
Spécificités de Caml
Opérateurs de comparaison
=
<>
<
>
<=
>=
let _ = "foo" = "foo"
- : bool = true
let _ = "foo" <> "foo"
- : bool = false
let _ = "foo" < "bar"
- : bool = false
let _ = "foo" > "foo"
- : bool = false
Attention L’opérateur « == » existe mais il ne faut pas l’utiliser.
Divers : les priorités en Caml (1)
L’application est notée par la simple juxtaposition de la fonction et
de son argument.
expr expr
Une suite d’applications est parenthésée par défaut à gauche.
expr expr expr
est équivalent à
(expr expr) expr
Divers : les priorités en Caml (2)
priorités décroissantes
1. application: f x
2. opérateur arithmétiques: f x + 1 est (f x) + 1
3. construction de n-uplets: f x + 1,2 est ((f x) + 1),2
4. abstraction: fun x -> f x + 1,2 est fun x -> (((f x) +
1),2)
Ne pas hésiter à mettre des parenthèses
L’opérateur - est par défaut la soustraction et pas l’opposé: f -1
est « f moins un ». Pour appliquer une fonction f à -1 il faut
écrire f (-1).
Curryfication
Comparer et typer les définitions :
let f (x,y) = x + 2*y
let g x y = x + 2*y
let _ = f (4,5)
- : int = 14
let _ = g 4 5
- : int = 14
Curryfication
▶
▶
f doit toujours être appelée avec une paire d’entiers comme
argument
g peut être appelée avec un entier ou deux
▶
g 4 est la fonction fun y -> 4 + 2*y
Application partielle
let compdbl f x = f (f x)
val compdbl : ('a -> 'a) -> 'a -> 'a = <fun>
let square x = x*x
val square : int -> int = <fun>
compdbl square 5
- : int = 625
let puiss4 = compdbl square
val puiss4 : int -> int = <fun>
Un exemple
Fonctionnelle d’itération
Ecrire une fonctionnelle qui correspond à la sémantique de
l’itération pour
▶
caractérisation à partir d’exemples de l’itération « pour »
▶
établissement des lois de récurrence
▶
écriture de la fonctionnelle repeter
▶
constat de l’efficacité de cette fonctionnelle
Exemples d’itérations
1. calcul itératif de 2n
2. calcul itératif du terme de rang n de la suite
▶
▶
u0 = 3
un = 3 ∗ un−1 + 5
3. calcul itératif du terme de rang n de la suite de fibonacci
▶
▶
▶
u0 = 0
u1 = 1
un = un−1 + un−2
La boucle classique
puiss := 1
pour i:=1 à n faire
puiss := puiss*2
init
U := 3
pour i:=1 à n faire
U := U*3+5
init
f
f
La sémantique de toute itération (1)
Similitudes:
▶
utilisation d’une variable v de cumul (U , puiss)
▶
initialisation de v
le contenu de la variable subit à chaque pas une
transformation v := f(v)
▶
▶
▶
f(x) = x*2 pour 2n
f(x) = x*3+5 pour la suite Un
La sémantique de toute itération (2)
Schéma général
x := x0
pour i := 1 à n faire
x := f(x)
La valeur à rendre est x. Elle dépend de x0, n et f.
On cherche F telle que x = F(n, f, x0)
La sémantique de toute itération (3)
valeur de x en fonction de x0, n et f :
n = 0 x = F(0, f, x0) = x0
n = 1 x = F(1, f, x0) = f(x0)
n = 2 x = F(2, f, x0) = f(f(x0))
…
cas général: x = F(n, f, x0) = fn (x0)
La loi de récurrence
F(n, f, x0) = fn (x0) = f(fn−1 (x0)) = f(F(n − 1, f, x0))
F est en fait la fonctionnelle repeter
▶
repeter n f x0 = x0 pour n = 0
▶
repeter n f x0 = f (repeter (n-1) f x0) pour n > 0
La fonctionnelle repeter
let rec repeter f x0 n =
if n = 0 then x0 else f (repeter f x0 (n-1))
val repeter : ('a -> 'a) -> 'a -> int -> 'a = <fun>
Utilisation de repeter
Utiliser l’itérateur repeter pour calculer
▶ 2n
▶ le terme de rang n de la suite
▶
▶
u0 = 3
un = 3 ∗ un−1 + 5
let puissde2 n = repeter (fun x -> x*2) 1 n
val puissde2 : int -> int = <fun>
let suite n = repeter (fun x -> 3*x+5) 3 n
val suite : int -> int = <fun>
On peut utiliser l’application partielle pour ne pas mentionner n
let puissde2 = repeter (fun x -> x*2) 1
val puissde2 : int -> int = <fun>
let suite = repeter (fun x -> 3*x+5) 3
val suite : int -> int = <fun>
Fibonacci itératif
Revenons à Fibonacci
U := 0; V := 1;
pour i:=2 à n faire
Y := V;
V := V+U;
U := Y;
Fibonacci itératif
Revenons à Fibonacci
U := 0; V := 1;
pour i:=2 à n faire
Y := V;
V := V+U;
U := Y;
let fibo n = snd (repeter (fun (x,y) -> (x+y,x)) (1,0) n)
val fibo : int -> int = <fun>
Cumuler les termes d’une suite
Définir
sigma(n) =
n
∑
Ui
i=0
en organisant les calculs de façon à diminuer la complexité.
acc:=0;
pour i:=0 à n faire
acc:=s+Ui
Cumuler les termes d’une suite
Définir
sigma(n) =
n
∑
Ui
i=0
en organisant les calculs de façon à diminuer la complexité.
acc:=0;
pour i:=0 à n faire
acc:=s+Ui
let sigma n =
snd(repeter
(fun (t,acc) -> (3*t+5,acc+t))
(3,0)
(n+1))
val sigma : int -> int = <fun>
Téléchargement