formations/algo/AlgoApprofondie/cours/modules.txt

517 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

La programmation modulaire
===========================
Il s'agit de décomposer un grand programme en
morceaux (**modules**) connectés entre eux par des **interfaces** bien
définies.
Ces modules doivent être aussi indépendants que possible.
.. glossary::
module
ensemble de ressources liées sémantiquement
interface
mode demploi du module, avec en plus un principe de masquage
des informations (partie publique, partie secrète)
signature
suite de déclarations, types, exceptions, valeurs, modules, etc.
implantation
suite de définitions, qui doit comporter tout ce qui est requis par la
signature
Type abstrait et langage de modules : la programmation modulaire
permet d'aller très loin dans la programmation structurée.
Définir des fonctions dans un fichier séparé
--------------------------------------------
Les fonctions peuvent être définies dans un fichier et le programme dans un
autre fichier séparé. Dans ce cas, pour pouvoir être exécuté directement avec
la commande python `nomfichierprogramme.py`, le fichier du programme doit
importer dabord les fonctions du fichier dans lequel les fonctions sont
définies.
1. Fichier de fonctions
~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
# Fichier foncmaxliste.py
# Recherche le premier élément maximal dans une liste ou
#dans une chaine de caractères
def max_list(L) :
k = len(L)
max, x = L[0], 0
i = 1
while i < k :
if max < L[i]:
max = L[i]
x = i
i = i + 1
return max, x
2. Fichier de programme
~~~~~~~~~~~~~~~~~~~~~~~~
Pour utilser les fonctions définies dans dautres fichiers, le fichier de
programme doit commencer par les instructions qui importent ces fichiers de
fonctions ou directement les fonctions de ces fichiers. Dans la syntaxe
ci-dessous, on importe une ou toutes les fonctions du fichier `foncmaxlist.py`.
.. code-block:: python
# Fichier progmaxlist.py
from foncmaxliste import max_list
# ou plus simple:
# from foncmaxliste import *
print max_list([4,5,6,9,12,5,10,3,18,5,6,7])
couple = max_list([4,5,6,9,12,5,10,3,18,5,6,7])
print Max de L est , couple[0]
print et se trouve à la position , couple[1]
print max_list(totovaaumarche)
couple = max_list(totovaaumarche)
print Max de L est , couple[0]
print et se trouve à la position , couple[1]
Au lieu dimporter les fonctions, on peut importer le fichier qui définit les
fonctions avec la syntaxe qui suit. Dans ce cas, le fichier de programme sera
changé comme suit :
.. code-block:: python
# Fichier prog2maxlist
import foncmaxliste
print foncmaxliste.max_list([4,5,6,9,12,5,10,3,18,5,6,7])
# la syntaxe indiquant le chemin dacces a la fonction max_list utiliser .
couple = foncmaxliste.max_list([4,5,6,9,12,5,10,3,18,5,6,7])
print Max de L est , couple[0]
print et se trouve à la position , couple[1]
print foncmaxliste.max_list(totovaaumarche)
couple = foncmaxliste.max_list(totovaaumarche)
print Max de L est , couple[0]
print et se trouve à la position , couple[1]
Lexécution directe du premier fichier de programme::
python prog max list.py
Lexécution directe du seconde fichier de programme::
python prog2 max list.py
Définition de l'implémentation d'un module
-------------------------------------------
Tout fichier qui contient au moins une définition dune fonction ou dune
variable est appelé un module (une bibliothèque). Le nom du module est le nom
du fichier enlevé le suffixe `.py`. Ainsi, un fichier de programme qui contient
au moins une définition dune fonction ou un fichier qui ne contient que des
définition de fonctions sont des modules. On peut importer un module ou des
fonctions ou variables dun module dans un programme, comme nous avons vu dans
les exemples ci-dessus.
.. important:: on peut importer un module, ou bien lancer un module en tant que
programme executable
.. code-block:: python
if __name__ == '__main__':
main()
Pour faciliter la programmation, Python définit un certain nombre de **modules internes**,
appelés les builtins (la librairie standard).
Par exemple :
Lors de louverture dune session interactive, on est dans un module interne nommé
main . Toutes les variables définies par affectation au niveau de ce module sont valides
globalement dans la session.
Dautres modules internes sont string, math, random
Dans une session de travail sous linterpréteur Python, la première importation dun mo-
dule qui, à part des fonctions quelle définit, contient des instruction de programme fait
exécuter ces instructions. Dans la même session, les importations suivantes ne font pas
exécuter ces instructions. Pour les exécuter, on utilise la fonction reload(nomdumodule)
(sans sufffixe .py).
Exemples d'interface
--------------------
::
type: son type
arguments
arg1 : description de l'argument 1
arg2 : description de l'argument 2
préconditions:
arg1 > 10
postconditions:
result < 19
raises: TypeError, AssertionError, SystemError...
test: tests nominaux pour chaque cas spécifié
- L'interface racine carrée
::
racine:
type: float -> float
arguments x: float, flottant dont on veut calculer la racine
pré: x >= 0
test: racine 25.0 -> 5.0 ; racine (-25) -> raises TypeError
- L'interface `lendemain`
Il faut définir auparavant un type spécifique appelé `date`
::
lendemain: le lendemain est la date qui désigne
le jour suivant de la date passée en argument
type: date -> date
arguments :
d: date
description: la date dont on veut calculer le lendemain
Le langages des modules
-------------------------
.. code-block:: ocaml
module type PILE = (* signature (interface) du module *)
sig
type a t
val create : unit -> a t
val push : a -> a t -> unit
val pop : a t -> a
end
(* implémentation du module *)
module Pile : PILE = (* le module est restreint
par la signature PILE *)
struct
type a t = a list ref
let create () = ref []
let push x p = p := x::!p
let pop p = match !p with [...]
let rec print p = match p with [...]
end
- `struct .. end` introduit une collection de définitions, valeurs, types ou modules.
C'est une **structure**.
- `module Nom = struct .. end` permet de donner un nom à cette structure et
c'est ça un module. C'est une structure nommée.
- `sig ... end` introduit une signature de module : une interface pour un module.
On restreint souvent une structure par une signature pour "cacher" certaines
définitions. Une signature de module fournit une **interface** entre l'extérieur
et l'intérieur d'un module.
En dehors du module, on accède à ses composants grâce à la notation pointée
.. code-block:: ocaml
let p = Pile.create()
Pile.push 45 p
**Signature inférée** : Lorsque la déclaration d'interface n'existe pas.
Contrainte de type par signature
------------------------------------
.. code-block:: ocaml
module M =
struct
type t = int * int * int
let make d m y = d,m,y
end
module M :
sig type t = int * int * int
val make : a -> b -> c -> a * b * c end
.. ifconfig:: exercice
ici la signature inférée est du type le plus général.
Cela peut poser des difficultés. Lesquelles ?
.. ifconfig:: correction
une incohérence entre type attendu et type obtenu
.. code-block:: ocaml
# let d = M.make 52 24 137 ;;
val d : int * int * int = (52, 24, 137)
.. code-block:: ocaml
module M =
struct
type t = int * int * int ;;
let make d m y = d, m, y ;;
end ;;
let d = M.make 8 5 8 ;;
module type S =
sig
type t ;;
val make : int -> int -> int -> t ;;
end ;;
module MS = (M:S) ;;
MS.make 5 1 2 ;;
autre exemple
.. code-block:: ocaml
module type DATE =
sig
type t = int * int * int
val mmax : int -> int -> int
val make : int -> int -> int -> t
val get_day : t -> int
val get_month : t -> int
val get_year : t -> int
end
on est alors obligé de contrôler la vraissemblance des valeurs dans chaque fonction du module, sinon le contrôle n'est pas maîtrisé...
.. code-block:: ocaml
M.get_month (23,45,67);;
- : int = 45
Le problème vient du fait que limplantation du type Date.t est **publique**.
Le Type abstrait de données permet de masquer limplantation du type.
.. code-block:: ocaml
module type DATE = sig
type t
val mmax : int -> int -> int
val make : int -> int -> int -> t
val get_day : t -> int
val get_month : t -> int
val get_year : t -> int
end
usage::
# module Date = (M:DATE) ;;
module Date : DATE
Le contrôle est maîtrisé::
Date.get_month (23,45,67);;
This expression has type int * int * int but is here used with type Date.t
Si (M:S) alors la structure M est une instance de la signa-
ture S
Type et signature
------------------
::
# module type A = sig
val a: int -> int
end ;;
module type A = sig val a : int -> int end
# module B = struct
let a x = x + 1 ;;
end;;
module B : sig val a : int -> int end
# module C = (B:A) ;;
module C : A
# C.a 2 ;;
- : int = 3
#
Module auquel on impose une signature
-----------------------------------------
::
module type DATE = sig
type t
val make: int -> t
val get_year: t -> int
val get_month: t -> int
end ;;
module MR = struct
type t = int * int
let make x y = (x, y)
let get_month (x, y) = x
let get_year (x, y) = y
end ;;
module date = (MR:DATE) ;;
Structure et signature
-----------------------
Une structure peut avoir plusieurs signatures
.. code-block:: ocaml
# module Cpt =
struct
let x = ref 0
let reset () = x := 0
let next () = incr x; !x
end
.. ifconfig:: exercice
Créer deux modules nayant pas les mêmes droits sans toucher à
limplantation.
.. ifconfig:: correction
une vue administrateur
.. code-block:: ocaml
module type ADM =
sig
val reset : unit -> unit
val next : unit -> int
end
::
module Adm = (Cpt:ADM)
# (* le compteur lui-même est invisible *)
# Adm.x;;
Unbound value Adm.x
une vue utilisateur
.. code-block:: ocaml
module type USR =
sig
val next : unit -> int
end
::
# module Usr = (Cpt:USR)
# Usr.next();;
- : int = 1
# Usr.reset();;
Unbound value Usr.reset
Contrainte partage du code (et du compteur) : impossible de construire deux composants administrateurs et utilisateurs autonomes.
- une unité qui contient les trois modules
- un composant qui publie les deux modules mais pas le module commun (un package)
L'héritage par inclusion
------------------------
.. code-block:: ocaml
# module Cpt2 = struct
include Cpt
let get() = !x
let next() = x := 2 * !x; !x
end ;;
module Cpt2 :
sig
val x : int ref
val reset : unit -> unit
val get : unit -> int
val next : unit -> int
end
Les foncteurs
--------------
Si un langage possède un langage de modules, on peut aller plus loin : on peut
considérer un module comme étant une expression de base du langage.
- La signature d'un module peut être considérée comme le type du module
- La structure du module peut être considéré comme sa valeur
Quel est l'intérêt ? On peut alors définir des **foncteurs**.
foncteur
"fonction" d'une structure vers une autre structure.
On peut ainsi paramétrer un module par un autre module.
.. code-block:: ocaml
module Nom (M1 :S1 ) (M2 :S2 ) (M3 :S3 ) ... =
struct
...
end
On applique un foncteur à des paramètres modules, pour
obtenir un nouveau module :
.. code-block:: ocaml
module M = F (Titi) (Toto)
exemple, le module Set::
module OrderedInt =
struct type t=int let compare = compare end ;;
module OrderedInt : sig type t = int val compare : a -> a -> int end
# module IntSet = Set.Make(OrderedInt)
module IntSet :
sig
type elt = OrderedInt.t
type t = Set.Make(OrderedInt).t
val empty : t
Ou, plus court::
module IntSet = Set.Make(struct type t=int let compare = compare end)
Le parallèle peut se faire entre les classes et les modules.
- module : Encapsulation données/traitements
- classes : différenciation données/traitements et possibilité de création de plusieurs instances
classes et types, inférences de types de classes