Skip to content

I. Les paradigmes impératif et fonctionnel


Cours

A. Les éléments de base de la programmation

Analysons le code Python suivant :

Code

l = [2, 6, 9]
s = 0
if l[0]%2 == 0:
    s = s + l[0]
if l[1]%2 == 0:
    s = s + l[1]
if l[2]%2 == 0:
    s = s + l[2]
###

Dans l'exemple ci-dessus, quels sont les éléments de base de la programmation utilisés ?

On identifie des affectations, des instructions conditionnelles. L'ensemble est une séquence d'instructions.

Que fait ce programme ?

Il calcule la somme des éléments pairs d'une liste Python.

Proposer une autre manière d'écrire ce programme. Quel autre élément de programmation utilisez-vous ?

Il est pertinent d'utiliser une boucle bornée.

Programme avec boucle

```python l = [2, 6, 9] s = 0 for i in range(len(l)): if l[i]%2 ==0: s = s+l[i]

Ecrire ce programme en utilisant deux fonctions intermédiaires.

```python def est_pair(n): return n%2 == 0

def somme(l):
    s = 0
    for i in range(len(l)):
        if est_pair(l[i]):
            s = s+l[i]

l = [2, 6, 9]
s = somme(l)

Il y a donc plusieurs manières de programmer un même algorithme, en utilisant différemment les éléments de programmation à notre disposition. Un paradigme de programmation est une manière de programmer spécifique.

B. Le paradigme impératif

Le paradigme impératif est le paradigme le plus courant, utilisé par de nombreux langages de programmation comme Python (qui intègre aussi d'autres paradigmes !). Il consiste à décrire un programme sous la forme de séquences d'instructions à effectuer dans un ordre précis.

Les caractéristiques de ce paradigme sont :

  • importance de l'ordre des instructions,
  • utilisation d'affectations de variables, d'expressions conditionnelles et de boucles.

Cette manière de programmer est proche du fonctionnement de la machine et peut sembler la plus intuitive.

Pourquoi utiliser d'autres paradigmes ?

On peut préférer d'autres manières de programmer en fonction du champ d'application du programme, du problème spécifique qui est traité, ou tout simplement des goûts du programmeur.

C. Le paradigme fonctionnel

Le paradigme fonctionnel est un paradigme rigoureux, permettant de plus facilement tester, déboguer et optimiser les programmes.

C.1. Les fonctions

Rappeler la syntaxe de définition et d'appel d'une fonction en Python :

Code

def nom_de_la_fonction(entree1,entree2):
    #séquence d'instructions
    (return sortie)

resultat = nom_de_la_fonction(val1, val2)

Le paradigme fonctionnel est centré, comme son nom l'indique, sur l'utilisation de fonctions. Plus précisément, on utilise des fonctions dites "pures" : des fonctions qui, étant donné des paramètres d'entrée, renvoient toujours la même sortie. Pour cela, il ne faut pas qu'elles créent d'effets de bord.

C.2. Les effets de bord

Définition

Un effet de bord correspond à la modification par une fonction d'une ou plusieurs variable(s) définie(s) en dehors de cette fonction.

Définition

Une variable définie en dehors d'une fonction est une variable globale, utilisable dans tout le programme. Une variable définie dans une fonction est par défaut locale : elle n'existe que dans cette fonction.

Exemple

Code

l = [0,1,2,3]
def remplace(x):
    y = l[0]
    l[0] = x
    return y

###

Que fait cette fonction ? En écrire la spécification (description des entrées et sorties).

Cette fonction remplace la valeur du premier élement de l par une valeur donnée en entrée.
Entrée : entier x remplaçant la valeur du premier élément de l
Sortie : l'entier y en première position de la liste l avant d'être remplacé

Que renvoie l'appel de remplace(1) ? Un deuxième appel de remplace(1) ?

Le premier appel renvoie 0, alors que le deuxième renvoie 1.

Que peut-on en conclure ?

Il y a modification de la variable l définie en dehors de remplace : elle crée un effet de bord. La fonction, appelée plusieurs fois avec le même paramètre ne renvoie pas la même valeur : ce n'est pas une fonction pure.

Proposer une fonction "pure" effectuant le même traitement que remplace.

###

Solution
def remplace(x, l):
    l2 = list(l)
    y = l2[0]
    l2[0] = x
    return y, l2

N.B. : une fonction pure renvoie toujours une valeur (sinon, vu qu'elle n'a pas d'effet de bord, elle ne servirait pas à grand chose...).

C.3. Les caractéristiques du paradigme fonctionnel

Les caractéristiques de ce paradigme sont :

  • utilisation de fonctions imbriquées (cf partie A),
  • utilisation de fonctions pures (donc pas d'effet de bord !),
  • on évite l'affectation de valeurs à des variables (utilisation de fonctions à la place).

Ces caractéristiques rendent les programmes plus fiables : différentes bonnes pratiques de programmation s'inspirent de cette manière de programmer.



Exercices

Exercice 1 :

A faire sur feuille.

Dans les exemples suivant, indiquer :
- s'il y a un effet de bord ou non,
- si la fonction est pure,
- s'il y a un effet de bord, proposer une autre version de la fonction qui l'élimine.

1.

Code

i = 5
def f():
    return i>5

2.

Code

def somme(a,b):
    s = a+b
    return s

3.

Code

x = 11
l = [1,3,5,7,9]
def remplace():
    if x>l[-1]:
        l[-1] = x
    return l

Exercice 2 :

A faire sur feuille.

Identifier les caractéristiques se rapportant au paradigme impératif, et celles se rapportant au paradigme fonctionnel.
- autorise les effets de bord,
- l'ordre des instructions n'a pas toujours d'importance,
- utilise des affectations de variables,
- utilise des boucles,
- découpe un programme en fonctions.

Exercice 3 :

A faire sur ordinateur, réponses sur feuille.

On définit deux fonctions qui ajoutent un élément à une liste Python. Elles appellent toutes les deux la méthode append, qui modifie la liste.

Code

def ajoute(l, elt):
    l.append(elt)
    return l

def ajoute1(l, elt):
    l.append(elt)
  1. Tester ajoute en définissant, dans le corps de votre programme, une liste l1 qui vaut [0, 1, 2, 3], puis en faisant l'appel l2 = ajoute(l1, 4). Que contiennent l1 et l2 après cet appel ?


  2. Tester ajoute1 en faisant l'appel ajoute1(l1, 4). Où se trouve le résultat de la fonction ?


    A retenir : Les listes en paramètres des fonctions Python sont passées par référence : on fait référence à la liste initiale, à son emplacement dans la mémoire. Par opposition, les types de base sont passés par valeur : on ne récupère que la valeur de la variable.

  3. Pour ne pas modifier la liste passée en paramètre, on se dit que l'on peut en faire en copie et modifier cette copie dans la fonction. On propose la fonction suivante :

    Code

    def ajoute2(l, elt):
        ls = l
        ls.append(elt)
        return ls
    

    Tester cette fonction avec l1 (attention, non-modifiée !). Que contient l2 après l'appel l2 = ajoute(l1, 4) ? Qu'en concluez-vous ?


  4. L'opération d'affectation ne crée pas une nouvelle liste, c'est toujours la même qui est manipulée. Il existe différentes solution pour créer une nouvelle liste avec les mêmes valeurs que la première. Tester les fonctions suivantes pour vérifier qu'elles remplissent bien l'objectif de ne pas modifier l :

    Code

    def ajoute3(l, elt):
        ls = l.copy()
        ls.append(elt)
        return ls
    
    def ajoute3(l, elt):
        ls = list(l)
        ls.append(elt)
        return ls
    
  5. Que faut-il donc faire pour éviter les effets de bord lorsque l'on manipule des listes dans des fonctions ?


Exercice 4 :

A faire sur ordinateur.

Les codes suivant correspondent à des algorithmes vus en première.
- Ecrire chacun sous la forme d'une fonction, dont on aura identifié les paramètres d'entrée et de sortie et dont on choisira le nom de manière adéquate. On veut des fonctions pures, n'utilisant pas de variables globales.
- Renommer les variables pour que l'on comprenne plus facilement leur rôle.
- Ajouter les spécifications des entrées et des sorties.

1.

Code

x = 0
y = 0
l = [6,8,7,1,0]
for i in range(len(l)):
    if l[i]>x:
        x = l[i]
        y = i
print(x, y)

2.

Code

o = 0
t = [6,2,4,8,2,1,3]
for e in t:
    if e == 2:
        o = o+1
print(o)

TP : Programmation fonctionnelle

Fichier de correction : Correction

Vous devez garder un compte-rendu de chaque TP : sauvegardez votre travail dans un ou plusieurs fichier(s) .py.

A. Les fonctions d'ordre supérieur

Les fonctions d'ordre supérieur sont des éléments caractéristiques de la programmation fonctionnelle : des fonctions qui prennent en entrée d'autres fonctions, ou bien qui retournent des fonctions en sortie.

A.1. La fonction map

La fonction map prend comme arguments une fonction et une liste d'arguments à appliquer à cette fonction. Elle permet de ne pas avoir à réécrire un appel de fonction plusieurs fois, lorsque l'on veut appliquer le même traitement à différentes données.

  1. Exécuter le code suivant et regarder la valeur de liste_int :

Code

liste_str = ["000","147","1369874"]
liste_int = list(map(int, liste_str))
  • On applique la fonction int aux différents éléments de la liste de chaînes de caractères pour les convertir.
  • La fonction map renvoie un itérateur, c'est pourquoi pour avoir la liste des résultats, on a appliqué la fonction list à cet itérateur.

  • En s'inspirant de l'exemple précédent, utiliser la fonction map pour convertir en str une liste d'entiers (vous choisirez un exemple).

  • Ecrire un code en paradigme impératif permettant de faire le même traitement qu'en 1. (comme si vous ne connaissiez pas la fonction map).

A.2. La fonction reduce

La fonction reduce de la bibliothèque functools sert à calculer une valeur à partir d'une séquence de valeurs. Elle va appliquer une fonction passée en argument à un premier couple de valeurs de la séquence, puis à un second couple, etc jusqu'à avoir utilisé tous les éléments de la séquence.

  1. Importer la fonction depuis la bibliothèque :

Code

from functools import reduce
  1. Consulter sa documentation en tapant dans la console :

Code

>>> help(reduce)
  1. Ecrire une fonction minimum renvoyant le minimum entre deux nombres passés en paramètre.

  2. Utiliser les informations que vous avez sur reduce pour appliquer la fonction sur la fonction minimum définie en 3., et une liste d'entiers. On doit obtenir en sortie le minimum de la liste des entiers.

  3. Ecrire un autre programme calculant le minimum d'une liste d'entiers, mais sans utiliser reduce.

B. Les fonctions anonymes, ou lambda

Les fonctions d'ordre supérieur peuvent prendre en paramètre des fonctions déjà définies, ou bien il est possible de créer une nouvelle fonction "sur le vif", sans la nommer. Ce sont des fonctions anonymes, déclarées avec le mot-clé lambda. La syntaxe est la suivante :

Mot-clé Paramètres d'entrée Valeur(s) de sortie
lambda x, y : (x+y,y)

C'est utile lorsque l'on ne réutilisera pas la fonction ensuite, qu'on a besoin d'utiliser une fonction rapidement.

Exécuter le code suivant et identifier ce qui est stocké dans la variable s :

Code

s = reduce(lambda x, y: x+y, [1,2,3,4,5,6,7,8,9])

Conclusion

Quels sont les intérêts d'utiliser le paradigme fonctionnel plutôt que le paradigme impératif ?