Connexion élèves

Choisir le(s) module(s) à installer :

Le paradigme fonctionnel ou la programmation fonctionnelle

Présentation du paradigme

Les règles

Le paradigme fonctionnel est une façon de programmer en respectant certaines règles (comme tous les autres paradigmes d'ailleurs...).
Ces règles peuvent être imposées par le langage de programmation, ou le codeur peut se les imposer lui même.
Les règles sont les suivantes :

  1. Tout doit (bien sur) être programmé dans des fonctions
  2. Les mêmes paramètres de fonction doivent toujours entrainer le même résultat pour éviter les effets de bord
  3. Les données sont immuables (ou non mutables) : une "variable" n'est jamais modifiée (et donc ce n'est plus "une variable" !)

Avant d'aller plus loin on peut remarquer que l'on respecte déjà une bonne partie du paradigme fonctionnel, en effet :

Nous allons donc tenter de comprendre l'intérêt du paradigme fonctionnel.

Mais pourquoi ?

La première remarque que l'on pourrait faire est donc :

C'est compliqué ...Pourquoi je ferai cela, ca marche très bien sans...

On peut tout d'abord penser que les bénéfices de la rigueur demandée par la programmation fonctionnelle sont surtout important pour les "gros" projets.
Mais on vous a également imposé certaines pratiques depuis que vous codez qui n'ont pas nécessairement d'intérêt "à notre échelle"...

De la troisième règle on peut remarquer que si on ne peut pas réaffecter une variable, il faudra en créer une nouvelle à chaque fois, cela va être très gourmand en mémoire...

C'est le cas, les langages fonctionnels sont gourmands en mémoire et ont vu leur essor avec l'apparition des gros volume de mémoire vive.
Ils sont passé de la recherche au domaine industriel dans les années 2000
Il est donc nécessaire de "nettoyer" la mémoire de nombreuses valeurs devenues inutiles ( c'est ce que l'on appelle le "ramasse miettes" = garbage collector )

Mais quel est l'intérêt de programmer ainsi, alors ?

Le principal avantage est la robustesse du code et la facilité de débogage.
(Ce n'est pas évident pour l'instant mais dans les gros projets avec des centaines de milliers de lignes de code, on passe souvent autant de temps, voir plus, à corriger les erreurs qu'a créer le code)

  • les données immuables simplifient le traitement parallèle du code et le multi-threading (retournez voir votre travail sur la gestion des processus)
  • les données immuables permettent également de mieux maîtriser la gestion de la mémoire, ce qui peut être critique dans certaines applications industrielles.

Et les "effets de bord" ?

Je vous ai parlé des "effet de bord" un peu plus haut (règle n°2), mais de quoi est-il réellement question ici ?
Contrairement à ce qu'on a pu déjà rencontrer, nous ne parlons pas ici d'effets dus, par exemple, aux limites d'un tableau de tableaux dont les premières et dernières lignes et colonnes doivent être traitées dans des cas à part comme ici.

On parle d'effet de bord en informatique quand une modification d'un programme cohérent entraîne un comportement non prévu à cause de :

  • la portée d'un variable
  • l'ensemble de définition d'une variable
  • la mauvaise définition d'un argument de fonction
  • la gestion des entrées/sorties
  • ...

L'expression "effet de bord" en français, vient de l'anglais side effect qui pourrait être traduite plus judicieusement par "effet indésirable".

Par exemple, en python, on dira que l'opérateur += ( comme dans a += 1 ) est à effet de bord car il modifie la variable à qui il s'applique "sans prévenir", ou en tout cas de façon non transparente ( on peut remarquer que ce n'est pas le cas de l'écriture : a = a + 1, qui recrée une nouvelle variable a ) .
Pour éviter tout ces effets, le paradigme fonctionnel va donc "interdire", ou limiter certaines pratiques :

Pour vous donner une idée d'un effet de bord sur un programme simple voici un exemple :

def del_pairs(tab: list)->list: ''' Cette fonction supprime les éléments pairs présents dans une liste et renvoie la liste modifiée ''' i = 0 while i < len(tab): if tab[i] % 2 == 0: tab.pop(i) # supprime l'élément d'index i dans la liste tab i = i+1 return tab liste = [1, 2, 4, 5, 8, 7, 9, 6, 8, 10]

Comme vous l'avez compris, cette fonction supprime les éléments pairs d'une liste.

  1. Testez le script avec la liste en exemple.
  2. Que constatez vous ?
  3. Quelle est la cause de cet effet de bord ?

SOLUTION

L'affichage d'une valeur à l'écran ou la saisie d'une valeur au clavier sont aussi des effets de bords...
Mais il sera difficile de s'en passer totalement...

Quel paradigme choisir ?

Vous avez programmé (au début sans le savoir) en programmation impérative, vous avez découvert cette année la programmation orientée objet et maintenant le paradigme fonctionnel.

Mais quel paradigme est le meilleur ?

En fait il n'y a pas de "bon" ou de "mauvais" paradigme.
Pour chaque situation il faut savoir choisir quel paradigme sera le plus adapté pour résoudre le problème posé dans une situation donnée.
Dans la plupart des cas, il est même intéressant de mélanger les paradigmes pour tirer le meilleur de chaque.

Bref il n'y a pas un seul outil qui résout tous les problèmes...

Programmation fonctionnelle en Python

Il est possible de suivre ces règles, et donc ce paradigme en Python. Les contraintes seront lourdes car Python, bien que langage multi-paradigme, n'est pas un langage fonctionnel et l'on devra s'interdire de nombreuses fonctionnalité très utiles !
Mais c'est quand même, possible. Essayons un peu :

Exemples simples

Pour chaque programme ci-dessous, vérifier si le paradigme fonctionnel est respecté.
Si ce n'est pas le cas, corriger le programme en respectant le paradigme fonctionnel.

#code n°1 def fct(): if i > 0: return True else : return False i = -3 print(fct())
#code n°2 def ajout(i): l.append(i) return l l = [4, 7, 3] ajout(2) print(l)
#code n°3 def incremente(): return x+1 x = 5 print(incremente())
#code n°4 def empile(ma_pile, valeur): ma_pile.append(valeur) return ma_pile print(empile([], 5))
#code n°5 def volume_sphere(r): pi = 3.1415 volume = (4/3)*pi*r**3 return volume print(volume_sphere(4))
#code n°6 def concatene(l1, l2): l1 += l2 return l1 print(concatene([1, 2], [3, 4]))

SOLUTION

Un projet un peu plus complet...

Maintenant que vous avez compris le principe sur des "petits bouts" de code, nous allons passer à un script un peu plus complexe :

	
from random import random

secondes = 10
smiley=['\U0001F600','\U0001F60A','\U0001F643']
positions_smiley = [1, 1, 1]

while secondes!=0:
    secondes -= 1
    print (secondes)
    for i in range(len(positions_smiley)):
        if random() > 0.3:
            positions_smiley[i] += 1
        print (smiley[i] * positions_smiley[i])
			

Ce code simule une petite course entre trois smiley et affiche à chaque seconde l'état de la course.
L'avancée se fait de façon aléatoire.
Nous allons progressivement faire évoluer ce code vers un un programme respectant le paradigme fonctionnel :

  1. Restructurer le code en le décomposant en fonctions :
    • deplacement_smiley qui incrémente de façon aléatoire le tableau positions_smiley.
    • affichage_smiley qui affiche la position des smiley.
    • une_seconde_de_course qui utilise les deux fonctions précédentes.
    • course qui simule la course complète.
  2. Restructurer le nouveau code en considérant les variables comme immuables.

    Pour faciliter votre travail fonctionnel et vous débarrasser des boucles for et de leur variable qui varie à chaque tour de boucle vous pouvez utiliser la fonction map.
    Elle permet d'appliquer une fonction à chaque élément d'un objet ittérable en suivant la syntaxe ci-dessous :

    	
    map(function, iterable)		
    						

    Pour une meilleur compréhension vous pouvez tester cette ligne de code :

    	
    l = [1,2,3]
    def f(a):
        if a == 2:
            return a+1
        else :
            return a
    
    g = list(map(f ,l))
    print(g)
    						
  3. Faire vérifier par un camarade que votre code vérifie bien le paradigme fonctionnel.

SOLUTION