5.2. Le style fonctionnel¶
La programmation fonctionnelle repose sur un certains nombres de principes qui se démarquent du style de programmation impératif que nous avons utilisé jusqu’à présent.
Les variables sont immuables et on privilégie donc la création de nouvelles valeurs plutôt que la modifications de données existantes.
La récursivité est favorisée dans la mesure du possible.
La composition de fonctions pures est encouragée pour réaliser des comportements complexes.
Les expressions, qui renvoient des valeurs, sont préférées aux déclarations.
Ces différents points visent à rendre la programmation fonctionnelle intuitive grâce aux fonctions pures, qui sont individuellement simples à comprendre et à tester.
5.2.1. Les fonctions pures¶
Une fonction est dite pure si elle remplit les conditions suivantes.
Elle est déterministe, c’est à dire qu’elle renvoie systématiquement la même valeur avec des paramètres identiques.
Sa valeur de retour est une nouvelle valeur et aucun des paramètres reçus n’est modifié.
Elle ne possède pas d’effet de bord, c’est-à-dire que rien n’est écrit ou lu à l’intérieur (y compris avec les fonctions
input()
,print()
, etc.).
5.2.2. Les fonctions d’ordre supérieur¶
Une fonction est dite d’ordre supérieur si elle en reçoit une autre en paramètre, ou elle en renvoie une comme valeur de retour. De telles fonctions sont fréquemment utilisées pour réaliser des comportements complexes en combinant des fonctions que s’appellent l’une l’autre.
5.2.3. Les fonctions anonymes¶
Il arrive que nous souhaitions déclarer une fonction comme s’il s’agissait d’une simple valeur, sans forcément la nommer, comme on peut le faire avec les types élémentaires tels que les chaînes de caractères.
sentence = "Hello World!" # approche nommée : on utilise une variable
print(sentence)
print("Hello World!") # approche anonyme : on utilise directement la valeur
Puisque les fonctions sont des structures immuables, les déclarer anonymement est plutôt naturel et peut se révéler pratique dans certaines situations.
C’est en particulier le cas lorsqu’elles sont relativement simples et ne possède qu’une seule expression dans leur corps.
On les déclare à l’aide l’instruction lambda
, dont la syntaxe est la suivante :
lambda arguments: expression
On peut utiliser lambda
pour créer une fonction nommée en assignant la fonction anonyme à une variable :
def power(x, y): return x ** y # paramètres `x` et `y`, expression `x * y`
On notera que ce code est identique à la déclaration de fonction classique :
def power(x, y):
return x ** y
Considérons à présent la déclaration d’une fonction réellement anonyme :
# la fonction `calc()` est d'ordre supérieur (prend une fonction en paramètre)
def calc(f, x, y):
return f(x, y)
# on fournit une fonction anonyme à `calc()`
print(calc(lambda x, y: x ** y, 2, 10))
1024
5.2.4. Les expressions génératrices¶
Les expressions génératrices servent à construire un générateur de manière simple, élégante et anonyme, selon le même principe que les listes en compréhension.
Les principales différences avec les listes en compréhension se retrouvent dans la syntaxe, car les expressions génératrices sont déclarées avec des parenthèses au lieu des crochets ([
, ]
), et dans l’utilisation de la mémoire.
Les listes sont construites dans leur intégralité au moment de leur déclaration alors que les générateurs renvoient une valeur à la fois, en les produisant à la demande.
Les générateurs sont donc bien plus efficace sur ce plan.
# générateur renvoyant le cube des nombres entiers pairs compris entre 0 et 19
gen = (x ** 3 for x in range(20) if x % 2 == 0)
for n in gen:
print(n)
0
8
64
216
512
1000
1728
2744
4096
5832