Demandez le programme !

Notion de paradigme de programmation

Définition et premiers exemples

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 :

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é :

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.

  1. Algorithme 1 :

    def decompter(n) -> None:
        if n >= 0: 
            print(n)
            decompter(n-1)
    decompter(9)
                    
  2. Algorithme 2 :

    for i in range(10):
        print(9-i)
                    
  3. 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()
                    
  4. 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>

Code de déblocage de la correction :

Cliquer sur le bouton Diminuer en dessous afin de visualiser l'effet du script de l'algorithme 4 de l'exercice précédent :

Diversité des langages de programmation

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.

Compléments sur la programmation fonctionnelle

Le premier langage de programmation fonctionnel (et impératif) est Lisp, créé en 1958.

Défauts de la programmation impérative

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
                
  1. Sans exécuter le script, deviner le résultat des deux appels ajouter(2) identiques successifs.

  2. Pourquoi l'exécution du même appel ne conduit pas au même résultat ?

  3. La fonction ajouter est-elle une fonction à effet de bord ?

Code de déblocage de la correction :

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
                
  1. Sans exécuter le script, deviner l'effet des deux appels doubler_tout([1, 4.5, 8, 7]) identiques successifs.

  2. La fonction doubler_tout est-elle une fonction à effet de bord ?

  3. Exécuter le code suivant :

    L = [1, 2, 3]
    doubler_tout(L)
    doubler_tout(L)
    print(L)
                    

    Que remarquez-vous ? Expliquez pourquoi.

Code de déblocage de la correction :

Programmation fonctionnelle

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 :

  1. la réaffectation y est interdite,

  2. 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.

  3. les boucles sont remplacées par des fonctions récursives,

  4. 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
                
  1. Cette fonction est_pair respecte-t-elle le paradigme fonctionnel ?

  2. Réécrire cette fonction de sorte qu'elle respecte désormais le paradigme fonctionnel.

Code de déblocage de la correction :

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 ?

Code de déblocage de la correction :

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 ?

Code de déblocage de la correction :

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    
                
  1. Cette fonction ajouter respecte-t-elle le paradigme fonctionnel ?

  2. Réécrire cette fonction de sorte qu'elle respecte désormais le paradigme fonctionnel.

Code de déblocage de la correction :

Pour trier une liste, vous avez découvert l'an dernier que vous pouvez utiliser soit la fonction sorted, soit la méthode sort.

  1. Peut-on utiliser la fonction sorted en paradigme fonctionnel ? Pourquoi ?

  2. Peut-on utiliser la la méthode sort en paradigme fonctionnel ? Pourquoi ?

Code de déblocage de la correction :

  1. 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

  2. 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

Code de déblocage de la correction :

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 :

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 :

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.

  1. 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é.

  2. 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.

  3. script suivant le paradigme de la programmation objet :

    1. 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.

    2. 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.

  4. script suivant le paradigme événementiel :

    1. 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.

    2. 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.

    3. 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.

    4. 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.

    5. Tester l'ensemble en prenant par exemple un temps.

    6. 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 :

  5. Code de déblocage du lien menant à la correction :

Licence Creative Commons
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