# Les fonctions d'ordre supérieur : des fonctions qui reçoivent et renvoient des fonctions<no value>

## Qu'allons-nous apprendre ?

- Ce qu'est une **fonction d'ordre supérieur** (Higher-Order Function).
- Comment **passer une fonction en paramètre** — et pourquoi sans parenthèses.
- Les fonctions intégrées **`map`** et **`filter`**.
- Le paramètre **`key`** de `sorted`, `max` et `min`.
- **Une fonction qui renvoie une fonction**.

## Pourquoi en avons-nous besoin ?

Deux fonctions presque identiques :

```python
def double_all(numbers):
    result = []
    for n in numbers:
        result.append(n * 2)
    return result

def square_all(numbers):
    result = []
    for n in numbers:
        result.append(n * n)
    return result
```

La boucle est **identique** — seule l'action sur chaque élément diffère. Chaque nouvelle action (moitié, plus 10, arrondi...) demande une fonction entière de plus, et c'est exactement la duplication que DRY interdit.

**L'idée :** écrire la boucle **une seule fois**, et passer **l'action elle-même** en paramètre.

## Explication à partir des bases

### Une fonction est un objet

Nous avons déjà vu dans la leçon « les fonctions en profondeur » qu'en Python une fonction est un **objet** — comme un nombre ou une liste :

- `double` (sans parenthèses) — **la fonction elle-même**. On peut la stocker dans une variable ou la transmettre.
- `double(5)` (avec parenthèses) — **un appel** à la fonction. Le résultat est ce qu'elle renvoie.

### La fonction d'ordre supérieur

Une **fonction d'ordre supérieur** est une fonction qui :

- **Reçoit** une autre fonction en paramètre, **ou**
- **Renvoie** une fonction.

Vous en avez déjà utilisé une : `sorted(products, key=lambda p: p[1])` — `sorted` reçoit une fonction dans le paramètre `key`.

{{< box "analogy" "Analogie : une machine à laver avec des programmes" >}}
Une machine à laver sait « passer sur tous les vêtements ». **Ce qu'il faut faire** à chaque pièce — vous le choisissez avec le programme : lavage délicat, normal ou séchage. On n'achète pas une nouvelle machine pour chaque programme — on lui donne le programme. La fonction d'ordre supérieur est la machine, et la fonction que vous passez est le programme.
{{< /box >}}

## Syntaxe

```python
def apply_to_all(numbers, action):     # action is a function
    result = []
    for n in numbers:
        result.append(action(n))       # call it for each item
    return result

apply_to_all([1, 2, 3], double)        # pass it: no parentheses
list(map(double, [1, 2, 3]))           # built-in: map
list(filter(is_even, [1, 2, 3, 4]))    # built-in: filter
sorted(words, key=len)                 # key: sort by the result of len
```

## Premier exemple de code

`week-2/apply_to_all.py` :

```python
def apply_to_all(numbers, action):
    result = []
    for n in numbers:
        result.append(action(n))
    return result

def double(n):
    return n * 2

print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))
```

**Sortie :**

```text
[2, 4, 6]
[1, 4, 9]
[2, 2]
```

## Analyse du code ligne par ligne

| Ligne | Explication |
|---|---|
| `def apply_to_all(numbers, action):` | `action` est un paramètre ordinaire — simplement, nous attendons qu'il contienne une **fonction**. |
| `result.append(action(n))` | Nous appelons la fonction reçue, sur chaque élément. |
| `apply_to_all([1, 2, 3], double)` | Nous passons `double` **sans parenthèses** — la fonction, pas son résultat. |
| `lambda n: n * n` | Une courte fonction sans nom, créée sur place. |
| `round` | On peut aussi passer une fonction intégrée. |

## Un autre exemple

**`map`, `filter` et `key`.** `week-2/hof_builtins.py` :

```python
prices = [100, 250, 40, 80]

with_fee = list(map(lambda p: p + 10, prices))
cheap = list(filter(lambda p: p < 100, prices))
print(with_fee)
print(cheap)

words = ["banana", "fig", "cherry", "kiwi"]
print(sorted(words, key=len))
print(max(words, key=len))

students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(max(students, key=lambda s: s[1]))
```

**Sortie :**

```text
[110, 260, 50, 90]
[40, 80]
['fig', 'kiwi', 'banana', 'cherry']
banana
('Dana', 92)
```

- **`map(func, items)`** — exécute `func` sur **chaque** élément. `list(...)` transforme le résultat en liste.
- **`filter(func, items)`** — ne garde que les éléments pour lesquels `func` renvoie `True`.
- **`key=len`** — `sorted` et `max` comparent selon **le résultat** de `len` sur chaque mot, et non selon le mot lui-même.

**Une fonction qui renvoie une fonction.** `week-2/make_multiplier.py` :

```python
def make_multiplier(factor):
    def multiply(n):
        return n * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))
```

**Sortie :**

```text
10
15
```

`make_multiplier` est une « usine » qui produit des fonctions : chaque appel renvoie une **nouvelle** fonction, qui se souvient de son propre `factor`. Comment elle s'en souvient exactement — nous le verrons dans la leçon suivante, sur les fonctions imbriquées.

## Erreur courante

**L'erreur : appeler la fonction au lieu de la passer.**

```python
apply_to_all([1, 2], double())
```

```text
TypeError: double() missing 1 required positional argument: 'n'
```

**Pourquoi ?** `double()` avec des parenthèses **appelle** la fonction immédiatement — sans paramètre — elle échoue donc avant même que `apply_to_all` ne commence. Passez la fonction **sans parenthèses** : `apply_to_all([1, 2], double)`. C'est la fonction qui la reçoit qui l'appellera.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Exercices

Les exercices n'utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d'abord seul, et n'ouvrez la solution qu'ensuite.

### Exercice 1: Une fonction en paramètre

Écrivez une fonction `apply_to_all(numbers, action)` qui exécute `action` sur chaque nombre et renvoie une nouvelle liste. Utilisez-la avec une fonction ordinaire `double`, avec une `lambda` qui met au carré, et avec `round`.

```python
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))
```

**Résultat attendu :**

```text
[2, 4, 6]
[1, 4, 9]
[2, 2]
```

{{< solution >}}
```python
def apply_to_all(numbers, action):
    result = []
    for n in numbers:
        result.append(action(n))
    return result


def double(n):
    return n * 2


print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))
```

```text
[2, 4, 6]
[1, 4, 9]
[2, 2]
```

Passez la fonction **sans parenthèses** (`double`, `round`). C'est `apply_to_all` qui l'appelle.
{{< /solution >}}

### Exercice 2: map et filter

```python
prices = [20, 75, 120, 45, 90]
```

1. Ne gardez que les prix supérieurs à 50, avec `filter`.
2. Ajoutez 10 à chaque prix restant, avec `map`.

**Résultat attendu :**

```text
[75, 120, 90]
[85, 130, 100]
```

{{< solution >}}
```python
prices = [20, 75, 120, 45, 90]

expensive = list(filter(lambda p: p > 50, prices))
raised = list(map(lambda p: p + 10, expensive))
print(expensive)
print(raised)
```

```text
[75, 120, 90]
[85, 130, 100]
```

`filter` ne garde que ce pour quoi la lambda renvoie `True`. `map` exécute la lambda sur chaque élément. `list(...)` transforme le résultat en liste.
{{< /solution >}}

### Exercice 3: key — selon quoi trier

```python
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
```

1. Triez les mots par longueur.
2. Trouvez le mot le plus long.
3. Triez les étudiants par note, de la plus haute à la plus basse, et n'affichez que les noms.

**Résultat attendu :**

```text
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avi
```

{{< solution >}}
```python
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]

print(sorted(words, key=len))
print(max(words, key=len))

for name, grade in sorted(students, key=lambda s: s[1], reverse=True):
    print(name)
```

```text
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avi
```

`key=len` — comparer selon **le résultat** de `len` sur chaque mot, et non selon le mot lui-même.
{{< /solution >}}

### Exercice 4: Une fonction qui renvoie une fonction

Écrivez une fonction `make_discount(percent)` qui renvoie une fonction, laquelle reçoit un prix et le renvoie après la remise.

```python
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))
```

**Résultat attendu :**

```text
180.0
100.0
```

{{< solution >}}
```python
def make_discount(percent):
    def apply(price):
        return price - price * percent / 100
    return apply


ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))
```

```text
180.0
100.0
```

`make_discount` est une « usine » — chaque appel produit une nouvelle fonction qui se souvient de son propre `percent`.
{{< /solution >}}

### Exercice 5: Appeler au lieu de passer

```python
def double(n):
    return n * 2

print(apply_to_all([1, 2], double()))
```

1. Quelle erreur obtiendrez-vous, et pourquoi ?
2. Quelle est la correction ?

{{< solution >}}
1. `TypeError: double() missing 1 required positional argument: 'n'`. Les parenthèses de `double()` **appellent** la fonction immédiatement — sans paramètre — avant même que `apply_to_all` ne commence.
2. Passez la fonction elle-même, **sans parenthèses** :

```python
def apply_to_all(numbers, action):
    result = []
    for n in numbers:
        result.append(action(n))
    return result


def double(n):
    return n * 2


print(apply_to_all([1, 2], double))
```

```text
[2, 4]
```
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **Fonction d'ordre supérieur** = une fonction qui reçoit une fonction ou renvoie une fonction. C'est possible parce qu'une fonction est un objet.
- Passez une fonction **sans parenthèses** — c'est la fonction qui la reçoit qui l'appelle.
- **`map`** — une action sur chaque élément. **`filter`** — garde ce qui remplit la condition.
- **`key=`** dans `sorted`, `max`, `min` — définit **selon quoi comparer**.
- Une fonction peut **renvoyer** une fonction — une « usine » qui produit des fonctions sur mesure.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Documentation officielle

- [Functional Programming HOWTO — Built-in functions](https://docs.python.org/3/howto/functional.html#built-in-functions)
- [sorted() — key](https://docs.python.org/3/builtins/functions.html#sorted)

<!-- docs:end -->
