Un paradigme de programmation est une manière de programmer basé sur un ensemble de principes.
Au cours des deux années de NSI, vous avez déjà travaillé, sans forcément vous en rendre compte, sur plusieurs paradigmes de programmation. Voici quelques paradigmes déjà rencontrés, nous les préciserons plus tard dans ce chapitre :
la programmation impérative : la suite des instructions du programme sont exécutées dans la machine dans l'ordre dans lequel elles ont été écrites.
la programmation fonctionnelle : les données ne sont modifiées dans le programme que par des fonctions et ces fonctions n'agissent que sur les variables saisies comme paramètres d'entrée en produisant des données de sortie.
la programmation Objet : le programme est vu comme l'interaction d'un ensemble d'objets ayant chacun des caractéristiques (les attributs) et supportant certaines actions (les méthodes).
la programmation événementielle : le programme sera défini par ses réactions aux différents évènements qui peuvent se produire : c'est ce que vous avez commencé à voir avec le Javascript.
... cette liste n'est pas exhaustive.
Chaque problème peut être résolu de différentes façons, mais parfois une manière de voir peut permettre de résoudre plus facilement ce problème : c'est que l'on a déjà vu avec un programme écrit soit en récursif (qui peut être vu dans certains cas comme un paradigme fonctionnel puisque l'on fait appel à des fonctions), soit en itératif (qui peut être vu dans certains cas comme un paradigme impératif).
Lors d'un repas, on peut avoir deux visions de la manière de manger pour arriver à satiété :
Vision impérative
Tant que j'ai encore faim :
prendre une bouchée de plus
mâcher
avaler
S'arrêter de manger
Ici, il y a une succession d'instructions dans un ordre précis. De plus, une variable booléenne "j'ai encore faim" évolue au cours de l'éxecution et détermine l'évolution du programme.
Vision fonctionnelle
fonction ai_je_encore_faim :
si oui :
prendre une bouchée de plus
mâcher
avaler
ai_je_encore_faim
sinon :
S'arrêter de manger
Ici, il y a une succession d'appels de fonctions dans un ordre précis. Plus une variable booléenne évolutive mais c'est le renvoi booléen de la fonction ai_je_encore_faim qui détermine l'évolution du programme.
Voici différents algorithmiques permettant l'affichage des 10 chiffres entiers dans l'ordre décroissant.
Préciser pour chacun des algorithme le type de paradigme auquel il correspond.
Algorithme 1 :
def decompter(n) -> None:
if n >= 0:
print(n)
decompter(n-1)
decompter(9)
Algorithme 2 :
for i in range(10):
print(9-i)
Algorithme 3 :
class Nombres():
def __init__(self,valeur):
self.valeur = valeur
def diminuer(self):
self.valeur -= 1
def __ge__(self):
if self.valeur >= 0:
return True
else:
return False
def __str__(self):
return str(self.valeur)
n = Nombres(9)
while n.__ge__() :
print(n.__str__())
n.diminuer()
Algorithme 4 :
<p id="lieu_affichage"></p>
<button onclick="decompte()">Diminuer</button>
<script>
let n = 9;
function decompte() {
if (n >= 0) {
document.getElementById('lieu_affichage').innerHTML += n + '<br>';
n = n - 1;
}
};
</script>
Cliquer sur le bouton Diminuer en dessous afin de visualiser l'effet du script de l'algorithme 4 de l'exercice précédent :
Le code machine qui explicitent toutes les instructions élémentaires et les échanges entre le(s) processeur(s), la mémoire et les entrées-sorties est forcément impératif.
(cf. cours de première sur l'architecture de Von Neumann et le langage assembleur)
Le langage impératif étant le langage "naturel" d'un ordinateur, les premiers langages de programmation étaient impératifs.
Or, le but d'un langage de programmation est de permettre au programmeur de plus facilement mettre en oeuvre sa pensée.
Comme l'être humain est riche de ses nombreuses manières de penser différentes, différents langages ont été créés et des fonctionnalités
ont été rajoutés progressivement aux langages de programmation déjà existant.
Le plus ancien langage de programmation haut niveau est le FORTRAN.
Créé en 1954. Il était a son origine purement impératif.
En 1958, la possibilité de créer et d'utiliser des fonctions a été rajoutée,
En 1991, la récursivité est permise dans ce langage,
En 2003, la programmation orientée objet est désormais supportée.
Il existe de nombreux langages de programmation.
Certains sont spécifiques à un paradigme de programmation :
Le langage BASIC créé en 1963 est un langage sans fonction ni objet : dans ce langage, on programme en impératif.
Le langage Haskell créé en 1990 est un langage purement fonctionnel.
D'autres permettent d'utiliser plusieurs paradigmes de programmation :
Python est un exemple de tel langage dit généraliste. D'où votre connaissance de plusieurs paradigmes en ne maîtrisant qu'un seul langage de programmation.
Le langage Caml est initialement un langage purement fonctionnel. Il a été augmenté de fonctionnalités
permettant une programmation impértive.
La variante Ocaml, qui est au programme de la classe préparatoire MPII, supporte aussi la programmation
orientée objet.
Le premier langage de programmation fonctionnel (et impératif) est Lisp, créé en 1958.
La programmation impérative permet par des instructions des changement d'état des variables. Cette mutabilité des données peut poser parfois problème.
Une fonction est dite à effet de bord si elle modifie un état autre que sa valeur de retour.
Par extension, un effet de bord est un effet secondaire non prévu dans l'exécution d'un programme cohérent. Souvent, ce comportement non prévu est dû à la non prise en compte de la portée des variables, des implicites sur des fonctions, ...
Voici une vidéo pour clarifier la notion d'effet de bord :
Voici un script
n=1
def ajouter(k):
"""fonction qui ajoute le nombre entier k entrée comme argument à la variable n
renvoie la somme obtenue"""
global n
n = n+k
return n
Sans exécuter le script, deviner le résultat des deux appels ajouter(2)
identiques
successifs.
Pourquoi l'exécution du même appel ne conduit pas au même résultat ?
La fonction ajouter
est-elle une fonction à effet de bord ?
Voici un script
def doubler_tout(L: list) -> None:
"""procédure qui double chaque élément d'une liste de flottants"""
for i in range(len(L)):
L[i] *= 2
print(L)
return None
Sans exécuter le script, deviner l'effet des deux appels doubler_tout([1, 4.5, 8, 7])
identiques
successifs.
La fonction doubler_tout
est-elle une fonction à effet de bord ?
Exécuter le code suivant :
L = [1, 2, 3]
doubler_tout(L)
doubler_tout(L)
print(L)
Que remarquez-vous ? Expliquez pourquoi.
La programmation fonctionnelle repose sur l'utilisation de fonctions et rejette la mutation des données. Tout le code repose sur l'évaluation de fonctions : les fonctions peuvent créer de nouvelles données, mais pas en modifier.
Principes du paradigme fonctionnel :
la réaffectation y est interdite,
toute fonction ne renvoie qu'une seule sortie, une seule est possible pour chaque n-uplet de valeurs donné comme argument d'entrée : la valeur renvoyée ne dépend pas de valeurs extérieures à la fonction.
les boucles sont remplacées par des fonctions récursives,
toute fonction peut être vue comme une donnée : on peut passer une fonction comme argument à une autre fonction ou renvoyer une fonction comme résultat d'une fonction ou définir une fonction dans une autre fonction.
Voici une fonction est_pair
:
n = 4
def est_pair() -> bool:
if n%2 == 0:
return True
else:
return False
Cette fonction est_pair
respecte-t-elle le paradigme fonctionnel ?
Réécrire cette fonction de sorte qu'elle respecte désormais le paradigme fonctionnel.
Voici une fonction sommer
:
def sommer(L: list) -> int:
"""L est une liste d'entiers.
Cette fonction renvoie la somme des éléments de la liste L"""
s = 0
for i in range(len(L)):
s += L[i]
return s
Cette fonction sommer
respecte-t-elle le paradigme fonctionnel ? Pourquoi ?
Voici une fonction somme
:
def somme(L: list, val: int) -> int:
"""L est une liste d'entiers.
Cette fonction renvoie la somme des éléments de la liste L avec val"""
if len(L) == 0:
return val
else:
return somme([L[i] for i in range(1, len(L))], val + L[0])
Cette fonction somme
respecte-t-elle le paradigme fonctionnel ?
Voici une fonction ajouter
:
def ajouter(L:list, val: int) -> list:
"""
L est une liste de nombres entiers ; val est un nombre entier.
Cette fonction renvoie la liste formée des éléments de L suivis de l'entier val
"""
L.append(val)
return L
Cette fonction ajouter
respecte-t-elle le paradigme fonctionnel ?
Réécrire cette fonction de sorte qu'elle respecte désormais le paradigme fonctionnel.
Pour trier une liste, vous avez découvert l'an dernier que vous pouvez utiliser soit la fonction sorted
, soit la méthode sort
.
Peut-on utiliser la fonction sorted
en paradigme fonctionnel ? Pourquoi ?
Peut-on utiliser la la méthode sort
en paradigme fonctionnel ? Pourquoi ?
En respectant le paradigme fonctionnel, créer une fonction max2
qui prend comme argument deux
entiers et qui renvoie le maximum de ces deux nombres
En respectant le paradigme fonctionnel, créer une fonction maxL
qui prend comme argument une liste non
vide de nombres entiers et qui renvoie le maximum de cette liste.
Vous pouvez utiliser la fonction max2
La programmation fonctionnelle est souvent considérée comme plus sûre car les effets de bords sont supprimés et l'immutabilité des données permet de mieux contrôler le déroulé d'un programme. Il est plus simple de prouver qu'un programme fonctionne correctement.
Cependant, comme le montre l'exercice sur la somme des valeurs d'une liste d'entiers, la programmation
fonctionnelle peut requérir beaucoup d'allocation mémoire. Si jamais, le programme n'a plus assez de mémoire à disposition alors
on ne peut plus garantir sa sûreté.
Du coup, les systèmes nécessitant une grande sécurité sont encore développés avec des langages impératifs plutôt que fonctionnels.
On s'intéresse à la modélisation du trafic internet au terminal informatique d'une petite entreprise.
On admet que le temps, en secondes, séparant l'arrivée de deux paquets de données à ce terminal vérifie les propriétés suivantes :
le temps moyen séparant l'arrivée de deux pâquets successifs à ce terminal est connu : moyenne
.
la probabilité qu'un paquet arrive au terminal dans les $h$ secondes qui suivent ne dépend pas du moment $t$ considéré.
En mathématiques, dans ces conditions, on dit que le temps séparant l'arrivée de deux paquets de données à ce terminal est une variable aléatoire suivant la loi exponentielle de paramètre $\dfrac{1}{\text{moyenne}}$.
Dans cet exercice, un compteur donnant le nombre de paquets arrivant au terminal dans un temps donné sera programmé dans quatre scripts différents, chacun suivant un des paradigmes de programmation suivant :
impératif
fonctionnel
programmation objet
événementiel
Dans tout cet exercice, vous admettrez et utiliserez le fait que, pour une moyenne connue valant moyenne
,
le temps séparant l'arrivée de deux paquets peut être obtenu par le renvoi de la fonction suivante :
from random import random
from math import log
def arrivee(moyenne: float) -> float:
"""
Prend en entrée la durée moyenne séparant le temps d'arrivée de deux paquets successifs.
renvoie le temps aléatoire à attendre pour l'arrivée d'un nouveau paquet
(d'après la loi exponentielle de paramètre 1/moyenne)
"""
return -log(random())*moyenne
Pour les trois scripts en langage Python, la durée moyenne séparant le temps d'arrivée de deux paquets successifs sera stockée dans une variable
moyenne
;
lors des tests, vous lui affecterez par exemple la valeur 0.05 ce qui signifie concrètement que 20 paquets arrivent chaque seconde en moyenne
au terminal.
script suivant le paradigme impératif :
Proposer un script en impératif qui utilise la fonction arrivee
, simule l'arrivée aléatoire
d'un certains nombres
de paquets pendant un temps temps_lim
et qui affiche le nombre de paquets arrivés durant ce laps de temps.
Ne pas se servir de la moyenne attendue mais utiliser le fait qu'un premier paquet va arriver avec
avec un dans d'attente aléatoire donné par la fonction arrivee
, puis un deuxième avec
un temps d'attente donné par la même fonction, et ainsi de suite jusqu'à ce que le temps imparti soit
écoulé.
script suivant le paradigme fonctionnel :
Proposer un script en fonctionnel qui utilise la fonction arrivee
, simule l'arrivée
aléatoire d'un certains nombres
de paquets au cours d'un temps temps_lim
et qui affiche le nombre de paquets arrivés
durant ce laps de temps.
script suivant le paradigme de la programmation objet :
Créer une classe Paquets
permet de :
créer une instance qui aura comme attributs le temps d'attente moyen entre deux arrivées, le nombre de paquets déjà arrivés, le temps écoulé et le temps total de l'expérience.
de gérer à travers des méthodes les attributs, l'arrivée simulée d'un paquet, le temps écoulé et l'affichage.
Tester cette classe, par exemple avec un temps moyen d'attente de 0.05 seconde pour une durée totale de mesure de 10 secondes.
script suivant le paradigme événementiel :
Créer deux boutons "Démarrer" et "Arrivée d'un paquet" ainsi qu'un élément hmtl identifié par "lieu_affich" où apparaîtront les affichages voulus.
Faire en sorte que le clic sur le bouton "Démarrer" conduise à l'exécution d'une fonction Javascript lancement
qui :
(ré)initialise un compteur nb
lequel stocke le nombre de paquets reçus,
efface tout éventuel affichage présent dans l'élément html identifié par "lieu_affich",
limite, à l'aide de la méthode setTimeout
, la durée de mesure.
setTimeout(fonction_en_attente, 10000);
conduit à la mise en place d'un décompte du temps :
dans 10000 millisecondes (soit 10 secondes) la fonction fonction_en_attente
sera exécutée.
Faire en sorte qu'un clic sur le bouton "Arrivée d'un paquet" conduise à l'exécution d'une fonction Javascript arrive
qui :
modélise l'arrivée d'un nouveau paquet,
affiche, dans l'élément html identifié par "lieu_affich", le nombre de paquets déjà arrivés.
Créer une fonction Javascript qui gère la fin du comptage du nombre de paquets arrivés en permet l'affichage d'une information pertinente.
Tester l'ensemble en prenant par exemple un temps.
Améliorer votre script en faisant en sorte :
qu'au démarrage seul le bouton "Démarrer" soit visible,
qu'une fois le bouton "Démarrer" cliqué, seul le bouton "Arrivée d'un paquet" soit visible le temps de la mesure,
qu'une fois le temps de la mesure écoulé, le bouton "Arrivée d'un paquet" s'efface et le bouton "Démarrer" réapparaisse : ainsi, une nouvelle mesure peut recommencer.
Vous pouvez utiliser la commande suivante :
document.getElementById("nom_identifiant").style.visibility = "état_visibilité";
En prenant pour "état_visibilité" "hidden" ou "visible", vous pourrez rendre caché ou visible l'élément html dont l'identifiant est donné par "nom_identifiant".
Vous pouvez tester les boutons ci-dessous pour visualiser ce qui est attendu au niveau interaction :
Les différents
auteurs mettent l'ensemble du site à disposition selon les termes de la licence Creative
Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0
International