Les fonctions et les procédures ================================ Préliminaire : rappel de théorie de l'information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Théorie de l'information (Claude Shannon, 1949), (ou théorie de la communication) Canal de transmission:: entrée -> récepteur -> émetteur -> sortie Description d'une procédure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ En programmation impérative, un programme est une suite d’instructions qui font évoluer l’état mémoire, le résultat est dans l’état final de la mémoire. - une procédure peut prendre des paramètres - elle modifie l'état courant du système - Déclaration des paramètes - Déclaration du corps - Appel de la procédure .. raw:: latex \begin{algorithm} \caption{Procédure de permutation de deux entiers}\label{permutation} \begin{algorithmic}[1] \Procedure{permuter}{$a,b$}{} \BState \emph{parametres}: \State $a: \textit{int}$ \State $b: \textit{int}$ \BState \emph{locales}: \State $z: \textit{int}$ \Comment{Une variable intermédiaire est nécessaire} \BState \emph{corps}: \State $z \gets a$ \State $a \gets b$ \State $b \gets z$ \EndProcedure \State \Call{permuter}{10, 12} \Comment{appel de la procédure} \end{algorithmic} \end{algorithm} effet de bord toute modification de la mémoire ou modification d'un support externe instruction commande ou phrase en mesure de modifier l'état du programme ou de la machine hôte (allocation mémoire, support externe, disque, écran...) Une procédure ne renvoie pas de valeur, mais provoque un 'effet de bord' (écriture dans une globale, dans un flux sortant etc.). Une procédure permet de créer une instruction nouvelle qui deviendra une primitive pour le programmeur. Cela permet de structurer le texte source du programme et améliorer sa lisibilité. Cela permet aussi d'appeler plusieurs fois, et à plusieurs endroit dans le code, cette primitive. Appel d'une procédure ~~~~~~~~~~~~~~~~~~~~~ (ex: pseudo-pascal) **déclaration de procédure** .. raw:: latex \begin{algorithm} \caption{Procédure de permutation de deux entiers}\label{appelpermutation} \begin{algorithmic}[1] \Procedure{permuter}{$a,b$}{} \BState \emph{parametres}: \State $a: \textit{int}$ \Comment{paramètres formels de la procédure} \State $b: \textit{int}$ \BState \emph{locales}: \State $z: \textit{int}$ \Comment{les variables locales de la procédure} \BState \emph{corps}: \State ... \Comment{Le corps de la procedure} \EndProcedure \State \Call{permuter}{10, 12} \Comment{l'appel de la procédure} \end{algorithmic} \end{algorithm} - les variables x1,...,xn sont appelées *paramètres formels* de p - les variables v1,...,vm sont appelées *les variables locales* de p les valeurs effectivement passées en paramètres, ici `10, 12` sont appelées **paramètres effectifs** de p signature C'est l'ensemble paramètre formel + resultat de l'appel fermeture L'ensemble procédure + variables locales + signature + parametres effectifs est appelé une **fermeture**. C'est la procédure + son contexte qui permet de l'instancier dans un programme. Environnement Contexte d’évaluation d'une expression ou d'une fonction Portée La portée d'un identifiant (une variable) est sa condition d'utilisation dans un contexte donné (utilisation locale uniquement, ou bien globale, ou bien locale et globale) La portée d’une liaison est la portion du code dans laquelle cette liaison est valide (i.e. où un identifiant est lié à une expression). .. ifconfig:: exercice **Exercice** : Que donne ce code ? .. code-block:: ocaml # let x = 42 in let y = x - 1 in x - y ;; .. ifconfig:: correction **Correction** : .. code-block:: ocaml - : int = 1 .. code-block:: ocaml let a = 3 (* première liaison pour l'identifiant a *) let b = 5 and c = 6 let somme = a + b + c val somme : int = 14 let a = 45 (* deuxième liaison pour l'identifiant a *) somme val a : int = 45 .. ifconfig:: exercice **Exercice** : Que donne ce code ? .. code-block:: ocaml let a = 3 and b = 4 and c = 8 ;; let somme = a + b + c ;; val somme : int = ??? let a = 44 let b = 5 let c = 1 somme - : int = ??? .. ifconfig:: correction .. code-block:: ocaml let a = 3 and b = 4 and c = 8 ;; - : int = 15 let somme = a + b + c ;; val somme : int = 15 let a = 44 let b = 5 let c = 1 somme - : int = 15 Même code en python .. code-block:: python >>> a = 1 >>> b = 2 >>> c = 3 >>> somme = a + b + c >>> somme 6 >>> a = 56 >>> b = 5678 >>> c = 56789 >>> somme 6 >>> Portée locale dans une expression .. code-block:: ocaml # let a = 2 and b = 3 and c = 4 in let somme = a+b+c in somme - : int = 9 # somme ;; Error: Unbound value somme # a ;; Error: Unbound value a .. important:: L’ordre d’évaluation dans un let ... in ... est bien déterminé, sans grande importance dans un cadre purement fonctionnel, mais important en cas d’effets de bord Exemple de variable globale modifiée localement (**attention, mauvaise pratique** !) : .. code-block:: python >>> a = 5 >>> def print_a(): ... print("La variable a = {0}.".format(a)) ... >>> print_a() La variable a = 5. >>> a = 8 >>> print_a() La variable a = 8. >>> niveau Le niveau d’une déclaration (de variable ou de procédure) est le nombre de procédures sous lesquelles elle est déclarée. Le programme principal a le niveau 0. .. code-block:: python :linenos: def _get_config(name): # return config value if not isfile(CONFIG_FILE): raise Exception("Fichier de configuration non existant") from ConfigParser import ConfigParser cfg = ConfigParser(allow_no_value=True) cfg.read(CONFIG_FILE) if name == "SUBNETS": return eval(cfg.get('eole', 'subnets')) # c'est une liste de tuple # FIXME elif name == "LEASES_FILE": DHCP_PATH = cfg.get('eole', 'container_path_dhcp') return join('/', DHCP_PATH, 'var/lib/dhcp/dhcpd.leases') def get_routes(*args, **kwargs): """ Send list of reserved IP return list of tuple (id, machine name, IP, MAC Adress) """ cfg = creole_loader(load_extra=True, rw=False, owner=MODNAME, mandatory_permissive=False) return zip(cfg.dhcp.dhcp.id_dhcp.id_dhcp, cfg.dhcp.dhcp.id_dhcp.hostname, cfg.dhcp.dhcp.id_dhcp.ip, cfg.dhcp.dhcp.id_dhcp.macaddress) On voit que l'objet `cfg` ligne 6 et 7 a le même nom que l'objet `cfg` ligne 19. C'est autorisé et les espaces de nommages sont différents. Description d'une fonction ~~~~~~~~~~~~~~~~~~~~~~~~~~ Une fonction renvoie une valeur et ne modifie pas l'état courant du programme en cours d'exécution ni ne réalise d'effets de bord. Elle renvoie **toujours** quelque chose (même la valeur ``None`` qui n'est pas rien) - une procédure peut prendre des paramètres - elle modifie l'état courant du système - Déclaration des paramètes - Déclaration du corps - Appel de la fonction En programmation fonctionnelle, programme est un ensemble de définitions de fonctions, un résultat est l'application d’une fonction à une structure de données effective. - composant de base : la fonction - opération de base : l’application .. raw:: latex \begin{algorithm} \caption{Exemple de fonction}\label{fonction} \begin{algorithmic}[1] \Function{permuter}{$a,b$}{} \Comment{définition de la fonction} \BState \emph{parametres}: \Comment{déclaration (noms, types) des paramètres formels} \State $a: \textit{int}$ \State $b: \textit{int}$ \BState \emph{locales}: \Comment{déclaration (noms, types) des valeurs locales} \State $z: \textit{int}$ \BState \emph{corps}: \State $z \gets a$ \State $a \gets b$ \State $b \gets z$ \BState \emph{return}: \Comment{La valeur, le résulat renvoyé par la fonction} \EndFunction \State \Call{permuter}{10, 12} \Comment{appel de la fonction} \BState \emph{result}: \State (12, 10) \Comment{Le résultat effectif de la fonction après appel} \end{algorithmic} \end{algorithm} Définition mathématique ~~~~~~~~~~~~~~~~~~~~~~~~~ fonction Une fonction f d’un ensemble E vers un ensemble F est une correspondance qui associe à chaque élément de E au plus un élément de F. - E est appelé le domaine de définition - F est appelé codomaine - la **signature** de la fonction : `E → F (int -> int = )` curryfication évaluation de l'application d'une fonction - évaluter `(f x y)` - peut donner une **valeur fonctionnelle** - évaluation de la valeur fonctionnelle sur une valeur des types de base :: let g = function n -> (function p -> p + 1) n;; Typage d'une fonction ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: ocaml # let f x y z = if x > 0 then y + x else z - x;; val f : int -> int -> int -> int = c’est en fait une fonction à un argument qui retourne une fonction:: .. code-block:: ocaml val f : int -> (int -> (int -> int)) = application de f à trois valeurs .. code-block:: ocaml # f 1 2 3;; - : int = 3 en programmation fonctionnelle, les fonctions sont des valeurs comme les autres .. code-block:: ocaml # fun x -> x * x;; - : int -> int = Récursivité ~~~~~~~~~~~~ .. code-block:: ocaml let rec fact n = if n=0 then 1 else n * fact (n-1) équivalent impératif utilisant une boucle .. code-block:: c int fact(int n){ int f = 1 ; int i = n ; while (i>0){ f = f * i; i-- ; } ; return f ; } Définitions par cas ~~~~~~~~~~~~~~~~~~~ .. code-block:: ocaml let rec fact n = match n with 0 -> 1 | -> n * fact (n-1) **exemple** : la fonction puissance .. code-block:: ocaml let rec puissance x n = match n with 0 -> 1 | -> x * puissance x (n-1) .. ifconfig:: exercice **Portée locale dans une fonction** Quelles sera la valeur de la variable `a` ? .. code-block:: python >>> a = 1 >>> def myfunc(): ... a = 2 ... return a + 1 ... >>> a = myfunc() + a .. ifconfig:: correction Correction: .. code-block:: python >>> a = 1 >>> def myfunc(): ... a = 2 ... return a + 1 ... >>> a = myfunc() + a >>> a 4 >>> .. ifconfig:: exercice **Exercice** : Portée locale dans une fonction avec variable globale Quelles sera la valeur de la variable `a` ? .. code-block:: python >>> a = 1 >>> def myfunc(): ... global a ... a = 2 ... return a + 1 ... >>> a = myfunc() + 3 >>> .. ifconfig:: correction **Correction** : .. code-block:: python >>> a = 1 >>> def myfunc(): ... global a ... a = 2 ... return a + 1 ... >>> myfunc() 3 >>> a 2 >>> a = myfunc() + 3 >>> a 6 >>>