Contenus | Capacités attendues | Commentaires |
---|---|---|
Paradigmes de programmation. | Distinguer sur des exemples les paradigmes impératif, fonctionnel et objet. | Choisir le paradigme de programmation selon le champ d’application d’un programme. Avec un même langage de programmation, on peut utiliser des paradigmes différents. Dans un même programme, on peut utiliser des paradigmes différents. |
La création du premier programme informatique par Ada Lovelace sur la machine de Babbage en 1842 a permis de formaliser les ingrédients des algorithmes tels que nous les connaissons: affectations, boucles, conditions Cependant, maintenant que l'informatique est présente dans très nombreux domaines, il s'est avéré nécessaire d'adapter la programmation aux problèmes à traiter. Ces approches appelées paradigmes de programmation fournissent au développeur une vue différente de la façon dont s'éxecute le programme, la programmation orientée objet en est un exemple bien connu.
Il existe trois grands types de programmation :
Même si certains langages forcent à utiliser un paradigme de programmation (ex: Smalltalk: POO, Haskell: fonctionnnelle), de nombreux langages modernes comme Python (ou Javascript) sont multiparadigmes et permettent la programmation impérative structurée, fonctionnelle et orientée objet; laissant ainsi le choix au programmeur du paradigme à utiliser en fonction du problème posé.
Pour illustrer les différences entre ces paradigmes, nous allons utiliser un exemple très simple issu de opensource.com.
On a une liste de caractères que nous souhaitons concaténer en une chaine de caractères.
ENTREE: entrée = ['p','y','t','h','o','n']
SORTIE: sortie = 'python'
La programmation impérative est un paradigme de programmation qui décrit les opérations en séquences d'instructions exécutées par l'ordinateur pour modifier l'état du programme.
La programmation impérative se concentre sur la description du fonctionnement d'un programme : le comment.
La plupart des langages de haut niveau comporte cinq types d'instructions principales :
if
, else
)for
, while
)GOTO
p.ex)entrée = ['p','y','t','h','o','n']
# on initialise une chaine vide pour la sortie
sortie = ""
# On concatène à l'aide d'une boucle
for c in entrée:
sortie = sortie + c
print(sortie)
python
Ce type de programmation est le plus ancien et utilisé, il est facile à comprendre, souvent efficace car proche des instructions réalisés par les processeurs. Par contre, il est assez difficile à tester car l'état du programme ne cesse de changer et il est difficile de tester une petite partie du programme au milieu de son exécution par exemple car elle ne nécessite que toutes les instructions précédentes aient déjà été appliquées correctement.
En programmation fonctionnelle on décrit les résultats que l'on veut obtenir à partir des données plutôt que la séquence d'instructions qui permettent d'obtenir les résultats (c'est un paradigme déclaratif).
L'approche fonctionnelle considère le calcul en tant qu'évaluation de fonctions mathématiques. Vous donnez vos données en entrée aux fonctions, qui vous renvoient les valeurs calculées en sortie.
L'utilisation massive de fonctions a amené à la création d'une syntaxe raccourcie pour la définition
de fonctions anonymes, les fonctions lambda
s :
lambda param1, ... , paramN: valeur_retournée
Au lieu de:
def ma_fonction(param1, ... , paramN):
...
return valeur_retournée
En programmation fonctionnelle, il n'y a pas d'état, l'opération d'affectation est interdite, ce qui permet de s'affranchir des effets secondaires (ou effets de bord).
Ceci rend les programmes parfaitement prédictibles, faciles à tester et à paralléliser, par contre il est souvent compliqué de se débarrasser complétement de l'état du programme.
Comme vu dans le chapitre précédent, en programmation fonctionnelle, on remplace souvent les boucles par des fonctions récursives. Une approche fonctionnelle par la récursion de notre problème pourrait être :
entrée = ['p','y','t','h','o','n']
def list_to_string(ma_liste, ma_chaine=""):
"""Fonction récursive pour concaténer les éléments d'une liste"""
if ma_liste:
# on enlève le premier élement de la liste
# qu'on ajoute à la chaine de caractères
ma_chaine += ma_liste.pop(0)
# application récursive
return list_to_string(ma_liste, ma_chaine)
else:
# cas de base
return ma_chaine
list_to_string(entrée)
Parmi les fonctions les plus représentatives de la programmation fonctionnelle, on trouve:
filter
, map
et reduce
(voir ici pour plus de
détails).
# on importe le module functools qui comporte
# les utilitaires de programmation fonctionnelle
import functools
entrée = ['p','y','t','h','o','n']
# on concatène avec la méthode reduce
sortie = functools.reduce(lambda s, c: s + c, entrée)
print(sortie)
python
On utilise ici reduce
une fonction d'ordre
supérieur très utilisée en
programmation fonctionnelle. Cette fonction applique une fonction de deux arguments de manière
cumulative aux éléments en séquence, de gauche à droite, pour réduire la séquence à une seule
valeur. Par exemple:
sample_list = [1,2,3,4,5]
import functools
somme = functools.reduce(lambda x, y: x + y, sample_list)
somme
15
((((1 + 2) +3) +4) +5)
15
Pour plus de méthodes fonctionnelles, consulter la documentation du module
functools
qui est utilisé pour des fonctions
de haut niveau : des fonctions qui agissent sur ou revoient d'autres fonctions.
Une implémentation et des exemples d'utilisation des fonctions filter
, map
, reduce
est
proposée en exercice.
La POO consiste en la définition et l'interaction de briques logicielles appelées objets; un objet représente un concept, une idée ou toute entité du monde physique, comme une voiture, une personne ou encore une page d'un livre.
Un objet possède:
class ListeLettres:
"Classe permettant de lier une chaîne de caractères à une liste de caractères"
def __init__(self, lettres=[]):
"""Intialisation de l'objet
Paramètres
----------
lettres: list
liste des caractères vide par défaut
"""
# initialisation des attributs de l'objet
self.lettres = lettres
# Conversion en chaine de caractères
self.string = ''.join(lettres)
# définition d'une méthode
def get_string(self):
return self.string
entrée = ['p','y','t','h','o','n']
# instanciation de l'objet avec les données de la liste
objet_py = ListeLettres(entrée)
# récupération de l'attribut string de l'objet
objet_py.string # renvoie 'python'
# récupération de l'attribut string grâce à la méthode get_string (préféré)
objet_py.get_string() # renvoie 'python'
Les différents principes de la conception orientée objet aident à la réutilisation du code, au masquage des données, etc., mais c'est une bête complexe, et comprendre toute la logique des objets et de leurs interactions est délicat et souvent difficile à tester en raison de ces interdépendances.
Nous aborderons plus en détail la programmation orientée objet au prochain chapitre.
Pour simplifier, si votre problème implique une série de manipulations séquentielles simples, suivre le paradigme de programmation impérative de la vieille école serait le moins cher en termes de temps et d'efforts et vous donnerait potentiellement les meilleures performances.
Dans le cas de problèmes nécessitant des transformations mathématiques des valeurs, le filtrage des informations, le mappage( transformer une liste en une autre) et les réductions( transformer une liste en une valeur), la programmation fonctionnelle pourrait être adaptée.
Si le problème est structuré comme un tas d'objets interdépendants avec certains attributs qui peuvent changer avec le temps, en fonction de certaines conditions, la programmation orientée objet sera certainement la plus naturelle.
Bien sûr, il n'y a pas de règle simple, car le choix du paradigme de programmation dépend également fortement du type de données à traiter, des connaissances des programmeurs et de diverses autres choses comme l'évolutivité.