# Booléens en programmation : une analyse à destination des enseignants

Sylvie Alayrangues, Olivier Baudon, Emmanuel Beffara, Ronan Charpentier, Sébastien Daniel, Christophe Declercq, Aslı Grimaud, Sébastien Hoarau, Anne Héam, Philippe Marquet, et al.

<figure><img src="image_1.png" /></figure>

## To cite this version:

Sylvie Alayrangues, Olivier Baudon, Emmanuel Beffara, Ronan Charpentier, Sébastien Daniel, et al. Booléens en programmation : une analyse à destination des enseignants. radix, 2024, 1. hal-03812698v2

HAL Id: hal-03812698

https://hal.science/hal-03812698v2

Submitted on 13 Jun 2024

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>
# Booléens en programmation : une analyse à destination des enseignants

Sylvie Alayrangues, Olivier Baudon, Emmanuel Beffara, Ronan Charpentier, Sébastien Daniel, Christophe Declercq, Aslı Grimaud, Sébastien Hoarau, Anne Héam, Philippe Marquet, et al.

<figure><img src="image_1.png" /></figure>

## To cite this version:

Sylvie Alayrangues, Olivier Baudon, Emmanuel Beffara, Ronan Charpentier, Sébastien Daniel, et al. " Booléens en programmation : une analyse à destination des enseignants. Recherches et recherches-actions en didactique de l'informatique, 2024, 1, pp.1-33. hal-03812698v2 "

HAL Id: hal-03812698

https://hal.science/hal-03812698v2

Submitted on 13 Jun 2024

HAL is a multi-disciplinary open access archive for the deposit and dissemination of scientific research documents, whether they are published or not. The documents may come from teaching and research institutions in France or abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents scientifiques de niveau recherche, publiés ou non, émanant des établissements d'enseignement et de recherche français ou étrangers, des laboratoires publics ou privés.

<figure><img src="image_2.png" /></figure>
Commission Inter-IREM Informatique

**Résumé.** La notion de booléen est fondamentale en informatique. Même si elle paraît simple à première vue, son introduction dans les premiers apprentissages de la programmation révèle un certain nombre de difficultés liées au fait qu'elle est associée à plusieurs concepts généraux de l'informatique : types de données, valeurs de vérité, invariants, structures de contrôle... Partant de ce constat, l'objectif de cet article est de proposer quelques réflexions sur le rôle des booléens en programmation. On détaille certains points relatifs aux "bonnes pratiques" possibles, en particulier dans le cadre de l'enseignement de l'informatique. On s'intéresse notamment au booléen comme type de données et aux principales structures de contrôle faisant appel à des booléens ainsi qu'à la notion générale d'expression booléenne utilisée en programmation. On identifie enfin quelques usages emblématiques des variables booléennes par l'intermédiaire d'exemples variés.

**Mots-clés.** booléen; logique; programmation; structure de contrôle; variable

**Abstract.** The notion of boolean is fundamental in computer science. Even if it seems simple at first sight, its introduction in the first stages of programming education reveals a number of difficulties related to the fact that it is linked to several general concepts of computer science: data types, truth values, invariants, control structures... Starting from this observation, the point of this article is to propose a reflection on the role of booleans in programming. We elaborate on some points concerning possible "good practices", especially in the context of computer science teaching. In particular, we focus on boolean as a data type and on the main control structures using booleans. We also discuss the general notion of boolean expression used in programming. Finally through a few examples, we illustrate some typical uses of boolean variables.

**Keywords.** boolean; logic; programming; control structure; variable

# 1. Introduction

Cet article est issu de réflexions de la Commission Inter-IREM « Informatique » (C3i)<fnref n="2" />, qui rassemble des membres de plusieurs groupes IREM<fnref n="3" />, enseignants de collège, de lycée, formateurs d'enseignants, universitaires. Ses travaux portent sur l'enseignement de l'informatique dans divers contextes scolaires ou d'enseignement supérieur. Ses productions incluent des analyses de sujets

<footnote>*Ont participé à la rédaction de cet article : Sylvie Alayrangues (IREM de Poitiers), Olivier Baudon (IREM de Bordeaux), Emmanuel Beffara (IREM de Grenoble), Ronan Charpentier (IREM de Caen), Sébastien Daniel (IREM de Lorraine), Christophe Declercq (IREM de Réunion), Aslı Grimaud (IREM de Lille), Sébastien Hoarau (IREM de Réunion), Anne Héam (IREM de Besançon), Philippe Marquet (IREM de Lille), Jean-Christophe Masseron (IREM de Paris), Antoine Meyer (IREM de Paris), Malika More (IREM de Clermont-Ferrand), Florence Nény (IREM de Marseille), Cécile Prouteau (IREM de Paris), Jean-Marc Vincent (IREM de Grenoble), Emmanuel Volte (IREM de Paris), Nathalie Weibel (IREM de Caen). Contact : emmanuel.beffara@univ-grenoble-alpes.fr</footnote>

<footnote n="2">²https://www.univ-irem.fr/-cii-informatique-</footnote>

<footnote n="3">³Instituts de Recherche sur l'Enseignement des Mathématiques, voir par exemple https://irem.u-paris.fr/lirem-de-paris/irem-de-paris-un-peu-dhistoire pour un bref historique.</footnote>
Commission Inter-IREM Informatique

**Résumé.** La notion de booléen est fondamentale en informatique. Même si elle paraît simple à première vue, son introduction dans les premiers apprentissages de la programmation révèle un certain nombre de difficultés liées au fait qu'elle est associée à plusieurs concepts généraux de l'informatique : types de données, valeurs de vérité, invariants, structures de contrôle... Partant de ce constat, l'objectif de cet article est de proposer quelques réflexions sur le rôle des booléens en programmation. On détaille certains points relatifs aux "bonnes pratiques" possibles, en particulier dans le cadre de l'enseignement de l'informatique. On s'intéresse notamment au booléen comme type de données et aux principales structures de contrôle faisant appel à des booléens ainsi qu'à la notion générale d'expression booléenne utilisée en programmation. On identifie enfin quelques usages emblématiques des variables booléennes par l'intermédiaire d'exemples variés.

**Mots-clés.** booléen; logique; programmation; structure de contrôle; variable

**Abstract.** The notion of boolean is fundamental in computer science. Even if it seems simple at first sight, its introduction in the first stages of programming education reveals a number of difficulties related to the fact that it is linked to several general concepts of computer science: data types, truth values, invariants, control structures... Starting from this observation, the point of this article is to propose a reflection on the role of booleans in programming. We elaborate on some points concerning possible "good practices", especially in the context of computer science teaching. In particular, we focus on boolean as a data type and on the main control structures using booleans. We also discuss the general notion of boolean expression used in programming. Finally through a few examples, we illustrate some typical uses of boolean variables.

**Keywords.** boolean; logic; programming; control structure; variable

# 1. Introduction

Cet article est issu de réflexions de la Commission Inter-IREM « Informatique » (C3i)<fnref n="2" />, qui rassemble des membres de plusieurs groupes IREM<fnref n="3" />, enseignants de collège, de lycée, formateurs d'enseignants, universitaires. Ses travaux portent sur l'enseignement de l'informatique dans divers contextes scolaires ou d'enseignement supérieur. Ses productions incluent des analyses de sujets

<footnote>*Ont participé à la rédaction de cet article : Sylvie Alayrangues (IREM de Poitiers), Olivier Baudon (IREM de Bordeaux), Emmanuel Beffara (IREM de Grenoble), Ronan Charpentier (IREM de Caen), Sébastien Daniel (IREM de Lorraine), Christophe Declercq (IREM de Réunion), Aslı Grimaud (IREM de Lille), Sébastien Hoarau (IREM de Réunion), Anne Héam (IREM de Besançon), Philippe Marquet (IREM de Lille), Jean-Christophe Masseron (IREM de Paris), Antoine Meyer (IREM de Paris), Malika More (IREM de Clermont-Ferrand), Florence Nény (IREM de Marseille), Cécile Prouteau (IREM de Paris), Jean-Marc Vincent (IREM de Grenoble), Emmanuel Volte (IREM de Paris), Nathalie Weibel (IREM de Caen). Contact : emmanuel.beffara@univ-grenoble-alpes.fr</footnote>

<footnote n="2">²https://www.univ-irem.fr/-cii-informatique-</footnote>

<footnote n="3">³Instituts de Recherche sur l'Enseignement des Mathématiques, voir par exemple https://irem.u-paris.fr/lirem-de-paris/irem-de-paris-un-peu-dhistoire pour un bref historique.</footnote>
d'épreuves nationales, des articles ou brochures portant sur l'enseignement de divers concepts informatiques, des communications ou ateliers, etc.

L'objectif de ce travail est de proposer quelques réflexions sur le rôle des booléens en programmation, de détailler certains points relatifs aux difficultés rencontrées par les élèves et leurs enseignants, et de mettre en lumière différentes pratiques et leurs impacts possibles dans l'enseignement. Le point de vue que nous développons est que, sous une apparente simplicité, la notion élémentaire de booléen est en réalité reliée à plusieurs autres concepts ou savoir-faire fondamentaux (et pour certains subtils) de l'informatique : typage et interprétation booléenne des objets, syntaxe d'un langage de programmation, contrôle du flot d'exécution d'un programme, anticipation ou analyse de l'état de la mémoire au cours de l'exécution, etc. Ceci nous semble justifier un soin particulier dans l'introduction de ces notions en classe (à divers niveaux) et dans la rédaction d'exercices, de solutions ou autres documents pédagogiques.

Ce texte ne présente pas de résultats de recherche en didactique de l'informatique, ni des exercices ou activités immédiatement utilisables en classe, mais une discussion générale des divers enjeux relatifs à cette notion. Il s'adresse aux enseignants de toute discipline concernée par l'algorithmique, la programmation, et plus généralement l'informatique, que ce soit au collège, au lycée ou dans l'enseignement supérieur. Le contenu se veut accessible à tout lecteur possédant des bases de programmation en Scratch ou Python. Nous proposons des réflexions de nature à intéresser des enseignants (en exercice ou en cours de formation), des concepteurs de sujets d'épreuves nationales (brevet, baccalauréat) ou de ressources à destination des enseignants (manuels, documents d'accompagnement,...), des formateurs, des membres de groupes IREM. Ce travail peut également suggérer des pistes de recherche à la communauté didactique.

Nous n'entendons pas non plus aborder ici tous les aspects de la logique telle qu'elle apparaît dans les programmes d'informatique ou de mathématiques. En particulier, les questions liées par exemple à la correction d'algorithmes ou à la logique des prédicats et à l'usage des quantificateurs en mathématiques ne sont pas abordées ici.

Après deux brefs exemples introductifs issus de sujets d'épreuves nationales (brevet et baccalauréat), nous décrivons la notion de booléen en tant que type de données (section 3). La section 4 aborde quelques usages typiques des booléens au sein des structures de contrôle classiques (conditionnelles et boucles). La section 5 approfondit la question des expressions booléennes composées d'opérateurs (comparaisons, opérateurs logiques, fonctions à valeur booléenne) et donne des exemples d'usages redondants fréquemment rencontrés. Enfin, la section 6 présente quelques autres archétypes de variables booléennes (indicateur, accumulateur, etc.).

## 2. Deux exemples tirés d'épreuves nationales

Nous amorçons notre discussion par la description de deux exemples d'exercices de programmation tirés des examens nationaux français.

### 2.1. Exercice tiré d'une épreuve de Brevet

L'énoncé reproduit ci-dessous est tiré du sujet de Diplôme national du brevet (DNB) de Polynésie, session de septembre 2018, en série professionnelle.

Il est possible de piloter la climatisation de son domicile à distance à l'aide de deux programmes.

1. Le programme A est le suivant :
d'épreuves nationales, des articles ou brochures portant sur l'enseignement de divers concepts informatiques, des communications ou ateliers, etc.

L'objectif de ce travail est de proposer quelques réflexions sur le rôle des booléens en programmation, de détailler certains points relatifs aux difficultés rencontrées par les élèves et leurs enseignants, et de mettre en lumière différentes pratiques et leurs impacts possibles dans l'enseignement. Le point de vue que nous développons est que, sous une apparente simplicité, la notion élémentaire de booléen est en réalité reliée à plusieurs autres concepts ou savoir-faire fondamentaux (et pour certains subtils) de l'informatique : typage et interprétation booléenne des objets, syntaxe d'un langage de programmation, contrôle du flot d'exécution d'un programme, anticipation ou analyse de l'état de la mémoire au cours de l'exécution, etc. Ceci nous semble justifier un soin particulier dans l'introduction de ces notions en classe (à divers niveaux) et dans la rédaction d'exercices, de solutions ou autres documents pédagogiques.

Ce texte ne présente pas de résultats de recherche en didactique de l'informatique, ni des exercices ou activités immédiatement utilisables en classe, mais une discussion générale des divers enjeux relatifs à cette notion. Il s'adresse aux enseignants de toute discipline concernée par l'algorithmique, la programmation, et plus généralement l'informatique, que ce soit au collège, au lycée ou dans l'enseignement supérieur. Le contenu se veut accessible à tout lecteur possédant des bases de programmation en Scratch ou Python. Nous proposons des réflexions de nature à intéresser des enseignants (en exercice ou en cours de formation), des concepteurs de sujets d'épreuves nationales (brevet, baccalauréat) ou de ressources à destination des enseignants (manuels, documents d'accompagnement,...), des formateurs, des membres de groupes IREM. Ce travail peut également suggérer des pistes de recherche à la communauté didactique.

Nous n'entendons pas non plus aborder ici tous les aspects de la logique telle qu'elle apparaît dans les programmes d'informatique ou de mathématiques. En particulier, les questions liées par exemple à la correction d'algorithmes ou à la logique des prédicats et à l'usage des quantificateurs en mathématiques ne sont pas abordées ici.

Après deux brefs exemples introductifs issus de sujets d'épreuves nationales (brevet et baccalauréat), nous décrivons la notion de booléen en tant que type de données (section 3). La section 4 aborde quelques usages typiques des booléens au sein des structures de contrôle classiques (conditionnelles et boucles). La section 5 approfondit la question des expressions booléennes composées d'opérateurs (comparaisons, opérateurs logiques, fonctions à valeur booléenne) et donne des exemples d'usages redondants fréquemment rencontrés. Enfin, la section 6 présente quelques autres archétypes de variables booléennes (indicateur, accumulateur, etc.).

## 2. Deux exemples tirés d'épreuves nationales

Nous amorçons notre discussion par la description de deux exemples d'exercices de programmation tirés des examens nationaux français.

### 2.1. Exercice tiré d'une épreuve de Brevet

L'énoncé reproduit ci-dessous est tiré du sujet de Diplôme national du brevet (DNB) de Polynésie, session de septembre 2018, en série professionnelle.

Il est possible de piloter la climatisation de son domicile à distance à l'aide de deux programmes.

1. Le programme A est le suivant :
<figure><img src="image_3.png" /></figure>

Indiquer ce qu'il se passe si la commande « Allumer la climatisation » est sélectionnée et que la température de la pièce est de 27°.

2. Le programme B permet de régler la puissance de la climatisation en fonction de la température. Ainsi lorsque la température est :

- supérieure à 28°C la climatisation est sur le niveau 2 ;

- entre 28°C et 25°C, la climatisation est sur le niveau 1 ;

- inférieure à 25°C, elle s'arrête.

Compléter les cases contenant des pointillés du programme B :

<figure><img src="image_4.png" /></figure>

On constate que cet exercice fait appel à une compréhension relativement fine de plusieurs concepts relatifs à l'usage des booléens :

* perception visuelle de la catégorie syntaxique des expressions booléennes en Scratch (hexagones verts) ;

* instructions conditionnelles imbriquées (cf. sec. 4.1) ;

* interprétation des comparaisons numériques (cf. sec. 5.1) ;

* négation implicite d'une expression booléenne (cf. sec. 5.2).

Le dernier point fait référence à la perception du statut des différentes variables dans les blocs « sinon » : par exemple, dans le programme A, l'instruction « mettre climatisation à 0 » est exécutée dans les cas où température ≤ 25. De même dans la question 2, il est nécessaire à la réalisation de l'exercice de pouvoir attribuer mentalement des propriétés au sujet de la valeur des variables (par exemple : « température est supérieure à ... » ou « température est comprise entre ... et ... ») à chacun des blocs.
<figure><img src="image_3.png" /></figure>

Indiquer ce qu'il se passe si la commande « Allumer la climatisation » est sélectionnée et que la température de la pièce est de 27°.

2. Le programme B permet de régler la puissance de la climatisation en fonction de la température. Ainsi lorsque la température est :

- supérieure à 28°C la climatisation est sur le niveau 2 ;

- entre 28°C et 25°C, la climatisation est sur le niveau 1 ;

- inférieure à 25°C, elle s'arrête.

Compléter les cases contenant des pointillés du programme B :

<figure><img src="image_4.png" /></figure>

On constate que cet exercice fait appel à une compréhension relativement fine de plusieurs concepts relatifs à l'usage des booléens :

- perception visuelle de la catégorie syntaxique des expressions booléennes en Scratch (hexagones verts) ;

- instructions conditionnelles imbriquées (cf. sec. 4.1) ;

- interprétation des comparaisons numériques (cf. sec. 5.1) ;

- négation implicite d'une expression booléenne (cf. sec. 5.2).

Le dernier point fait référence à la perception du statut des différentes variables dans les blocs « sinon » : par exemple, dans le programme A, l'instruction « mettre climatisation à 0 » est exécutée dans les cas où température ≤ 25. De même dans la question 2, il est nécessaire à la réalisation de l'exercice de pouvoir attribuer mentalement des propriétés au sujet de la valeur des variables (par exemple : « température est supérieure à ... » ou « température est comprise entre ... et ... ») à chacun des blocs.
Ces observations nous semblent justifier, dès le collège, une réflexion suffisamment approfondie
sur ces sujets.

**2.2. Exercice tiré de l'épreuve pratique de NSI**

L'exercice suivant est tiré du sujet n°6 de l'épreuve de l'enseignement de spécialité Numérique et Science Informatique (NSI) session 2022, partie pratique.

La fonction recherche prend en paramètre deux chaînes de caractères gene et seq_adn et renvoie True si on retrouve gene dans seq_adn et False sinon.

Compléter le code Python ci-dessous pour qu'il implémente la fonction recherche.

```python
def recherche(gene, seq_adn):
    n = len(seq_adn)
    g = len(gene)
    i = ...
    trouve = False
    while i < ... and trouve == ... :
        j = 0
        while j < g and gene[j] == seq_adn[i+j]:
            ...
            if j == g:
                trouve = True
            ...
    return trouve
```

Exemples :

```python
>>> recherche("AATC", "GTACAAATCTTGCC")
True
>>> recherche("AGTC", "GTACAAATCTTGCC")
False
```

Cet exercice nécessite une bonne compréhension de la structure complexe de ce programme,
des comparaisons utilisées dans les boucles while ainsi que dans la structure conditionnelle if (cf.
sec. 4). Il requiert également de comprendre l'utilisation faite dans le programme de la variable
booléenne trouve, qu'on appelle un « drapeau de continuation de boucle » (cf. sec. 6.3). Ce
programme illustre également un usage typique de l'aspect dit « séquentiel » de l'opérateur and (cf.
sec. 5.2). En outre, on peut considérer que le style de programmation qui transparaît dans cet extrait
de programme dénote un certain nombre d'habitudes et de principes qui peuvent être discutés, et
méritent a minima d'être repérés (cf. sec. 5.4).

**3. Booléen comme type de données**

Dans cette partie, on décrit la notion de booléen du point de vue informatique. Ce qui la
caractérise et la distingue de la notion mathématique d'algèbre de Boole est son statut de type de
données, c'est-à-dire d'objet susceptible d'être calculé, stocké et utilisé dans la dynamique d'un
programme.
Ces observations nous semblent justifier, dès le collège, une réflexion suffisamment approfondie
sur ces sujets.

## 2.2. Exercice tiré de l'épreuve pratique de NSI

L'exercice suivant est tiré du sujet n°6 de l'épreuve de l'enseignement de spécialité Numérique et Science Informatique (NSI) session 2022, partie pratique.

La fonction recherche prend en paramètre deux chaînes de caractères gene et seq_adn et renvoie True si on retrouve gene dans seq_adn et False sinon.

Compléter le code Python ci-dessous pour qu'il implémente la fonction recherche.

```python
def recherche(gene, seq_adn):
    n = len(seq_adn)
    g = len(gene)
    i = ...
    trouve = False
    while i < ... and trouve == ... :
        j = 0
        while j < g and gene[j] == seq_adn[i+j]:
            ...
            if j == g:
                trouve = True
            ...
    return trouve
```

Exemples :

```python
>>> recherche("AATC", "GTACAAATCTTGCC")
True
>>> recherche("AGTC", "GTACAAATCTTGCC")
False
```

Cet exercice nécessite une bonne compréhension de la structure complexe de ce programme,
des comparaisons utilisées dans les boucles while ainsi que dans la structure conditionnelle if (cf.
sec. 4). Il requiert également de comprendre l'utilisation faite dans le programme de la variable
booléenne trouve, qu'on appelle un « drapeau de continuation de boucle » (cf. sec. 6.3). Ce
programme illustre également un usage typique de l'aspect dit « séquentiel » de l'opérateur and (cf.
sec. 5.2). En outre, on peut considérer que le style de programmation qui transparaît dans cet extrait
de programme dénote un certain nombre d'habitudes et de principes qui peuvent être discutés, et
méritent a minima d'être repérés (cf. sec. 5.4).

## 3. Booléen comme type de données

Dans cette partie, on décrit la notion de booléen du point de vue informatique. Ce qui la
caractérise et la distingue de la notion mathématique d'algèbre de Boole est son statut de type de
données, c'est-à-dire d'objet susceptible d'être calculé, stocké et utilisé dans la dynamique d'un
programme.
### 3.1. La notion de booléen et sa transposition en programmation

Le type *booléen* est le type de données comprenant deux valeurs, habituellement appelées *vrai* et *faux*.

Certains langages de programmation disposent d'un type explicite pour représenter cette structure (Python, C++, Java, etc.) et d'autres utilisent d'autres types pour la représenter (versions anciennes du C, shell Unix, etc.). Quoi qu'il en soit, la notion de booléen est toujours présente d'une façon ou d'une autre dans tous les langages de programmation.

En Scratch, les expressions booléennes sont distinguées visuellement : elles sont marquées par des blocs de contour hexagonal et non arrondi comme les expressions arithmétiques. Cette forme d'expressions est imposée dans les structures conditionnelles.

En Python, la documentation précise qu'il existe des valeurs primitives *True* et *False* mais que les valeurs de la plupart des autres types peuvent aussi être interprétées comme valeurs de vérité, selon des conventions assez générales (ce qui ressemble à zéro ou à un ensemble vide est considéré comme faux, le reste est considéré comme vrai). Cela permet d'utiliser n'importe quelle expression comme condition dans une structure conditionnelle, comme dans l'exemple suivant où *liste*, si elle est non vide est interprétée comme *True* :

```python
if liste:
    print("la liste commence par", liste[0])
else:
    print("la liste est vide")
```

Les règles précises de cette interprétation des objets en tant que booléens sont un peu complexes. On peut en outre considérer que cette pratique rend le code moins explicite et moins facile à interpréter, elle est donc évitée par certains enseignants. Une table en annexe 9.2 détaille la situation.

Ainsi, certains langages pratiquent des conversions implicites entre types, par exemple Python interprète un nombre comme un booléen s'il apparaît comme condition. Ce phénomène n'apparaît pas en Scratch du fait de la structure du langage (Scratch n'autorise pas l'introduction de blocs non hexagonaux dans les opérateurs booléens). À l'inverse, Scratch peut interpréter un booléen comme un nombre s'il apparaît dans une expression arithmétique (Scratch autorise l'introduction de blocs hexagonaux dans les opérateurs numériques ou alphanumériques).

En Scratch, il n'y a donc jamais de conversion implicite vers une valeur booléenne. Par contre, les expressions booléennes peuvent être utilisées dans les autres expressions, elles sont alors converties implicitement, en nombres (1 pour *vrai*, 0 pour *faux*) ou en textes (*true* ou *false*) selon l'opérateur utilisé<fnref n="4" />.

<figure><img src="image_5.png" /></figure>

Python a un comportement similaire dans le cas numérique, où *False* est interprété comme 0 et *True* comme 1 :

```
>>> True + True
2
>>> (80 > 50) + (60 > 50)
2
```

<footnote n="4">⁴Pour le détail des conversions implicites utilisées par Scratch 3.0, voir par exemple cet extrait du code source de la machine virtuelle Scratch : https://github.com/LLK/scratch-vm/blob/develop/src/util/cast.js. L'interpréteur Scratch est écrit en Javascript mais n'utilise pas exactement les mêmes conventions que celui-ci.</footnote>
### 3.1. La notion de booléen et sa transposition en programmation

Le type *booléen* est le type de données comprenant deux valeurs, habituellement appelées *vrai* et *faux*.

Certains langages de programmation disposent d'un type explicite pour représenter cette structure (Python, C++, Java, etc.) et d'autres utilisent d'autres types pour la représenter (versions anciennes du C, shell Unix, etc.). Quoi qu'il en soit, la notion de booléen est toujours présente d'une façon ou d'une autre dans tous les langages de programmation.

En Scratch, les expressions booléennes sont distinguées visuellement : elles sont marquées par des blocs de contour hexagonal et non arrondi comme les expressions arithmétiques. Cette forme d'expressions est imposée dans les structures conditionnelles.

En Python, la documentation précise qu'il existe des valeurs primitives *True* et *False* mais que les valeurs de la plupart des autres types peuvent aussi être interprétées comme valeurs de vérité, selon des conventions assez générales (ce qui ressemble à zéro ou à un ensemble vide est considéré comme faux, le reste est considéré comme vrai). Cela permet d'utiliser n'importe quelle expression comme condition dans une structure conditionnelle, comme dans l'exemple suivant où *liste*, si elle est non vide est interprétée comme *True* :

```python
if liste:
    print("la liste commence par", liste[0])
else:
    print("la liste est vide")
```

Les règles précises de cette interprétation des objets en tant que booléens sont un peu complexes. On peut en outre considérer que cette pratique rend le code moins explicite et moins facile à interpréter, elle est donc évitée par certains enseignants. Une table en annexe 9.2 détaille la situation.

Ainsi, certains langages pratiquent des conversions implicites entre types, par exemple Python interprète un nombre comme un booléen s'il apparaît comme condition. Ce phénomène n'apparaît pas en Scratch du fait de la structure du langage (Scratch n'autorise pas l'introduction de blocs non hexagonaux dans les opérateurs booléens). À l'inverse, Scratch peut interpréter un booléen comme un nombre s'il apparaît dans une expression arithmétique (Scratch autorise l'introduction de blocs hexagonaux dans les opérateurs numériques ou alphanumériques).

En Scratch, il n'y a donc jamais de conversion implicite vers une valeur booléenne. Par contre, les expressions booléennes peuvent être utilisées dans les autres expressions, elles sont alors converties implicitement, en nombres (1 pour *vrai*, 0 pour *faux*) ou en textes (*true* ou *false*) selon l'opérateur utilisé<fnref n="4" />.

<figure><img src="image_5.png" /></figure>

Python a un comportement similaire dans le cas numérique, où *False* est interprété comme 0 et *True* comme 1 :

```
>>> True + True
2
>>> (80 > 50) + (60 > 50)
2
```

<footnote n="4">⁴Pour le détail des conversions implicites utilisées par Scratch 3.0, voir par exemple cet extrait du code source de la machine virtuelle Scratch : https://github.com/LLK/scratch-vm/blob/develop/src/util/cast.js. L'interpréteur Scratch est écrit en Javascript mais n'utilise pas exactement les mêmes conventions que celui-ci.</footnote>
Bien entendu, il n’est absolument pas conseillé d’utiliser les conversions implicites des booléens avec des élèves. Nous les mentionnons car elles peuvent aider à comprendre certaines erreurs.

## 3.2. Les booléens sont des valeurs

Quel que soit le langage de programmation considéré avec les conventions qui s’y appliquent, des booléens sont utilisés dans les structures de contrôle qui impliquent une forme de condition. Comme on le verra plus loin, la détermination d’une condition peut impliquer des calculs de toutes natures. De fait, les booléens sont calculés, ils ont donc un rôle de valeurs, au même titre que les nombres et les chaînes de caractères par exemple.

En particulier, les valeurs booléennes étant des valeurs comme les autres, il est possible de les stocker en les affectant à des variables. Même dans les langages qui ne disposent pas d'un type spécifique pour les booléens (comme Scratch ou bien C), utiliser une représentation, comme un entier 0 ou 1, permet de stocker une information de nature booléenne. On désigne parfois sous le nom de variable booléenne une variable utilisée pour stocker une information booléenne, qu'elle utilise ou pas un type spécifique aux booléens.

Parmi les différents rôles qu'on peut attribuer à une variable booléenne (voir par exemple Sajaniemi (2002)), on rencontre parfois le terme *drapeau*, calqué de l'anglais *flag*, pour désigner une telle variable, en particulier lorsqu'elle sert à indiquer si une condition a été satisfaite ou pas. Un cas classique consiste par exemple à utiliser une variable *est_trouve* dont la valeur de vérité indique si un élément donné a été rencontré lors du parcours d'un tableau.

La section 6 précise plus en détail et illustre différents rôles que peuvent jouer les variables booléennes.

Comme on le verra dans la suite, le fait que les booléens soient des valeurs comme les autres, qui peuvent être stockées, calculées, combinées par des opérateurs spécifiques, mais aussi prises comme valeurs d'entrée et renvoyées comme valeurs de sortie dans des fonctions, explique à la fois la puissance cette notion et les difficultés conceptuelles qui s'y rapportent, notamment à cause du rôle particulier que jouent les booléens dans les structures de contrôle.

# 4. Structures de contrôle

Le but de cette partie est de mettre en évidence les différentes utilisations fondamentales des booléens dans les structures de contrôle et de décrire les différents cas d'usage pour les variables booléennes.

## 4.1. Structures conditionnelles

### Structure conditionnelle simple

Une des premières rencontres des booléens en programmation est leur usage en tant que condition dans des branchements conditionnels. Les structures « si ... alors ... » et « si ... alors ... sinon ... » se retrouvent en effet dans la plupart des langages.

Voici un exemple classique d'exécution conditionnelle en Python :

```python
s = input("Entrer un nombre entier")
n = int(s)
if n < 0:
    print("Le nombre entré est strictement négatif")
```

Le message à la dernière ligne du programme ne sera affiché que si le nombre n est strictement négatif. La version suivante présente une alternative :
Bien entendu, il n’est absolument pas conseillé d’utiliser les conversions implicites des booléens avec des élèves. Nous les mentionnons car elles peuvent aider à comprendre certaines erreurs.

## 3.2. Les booléens sont des valeurs

Quel que soit le langage de programmation considéré avec les conventions qui s'y appliquent, des booléens sont utilisés dans les structures de contrôle qui impliquent une forme de condition. Comme on le verra plus loin, la détermination d'une condition peut impliquer des calculs de toutes natures. De fait, les booléens sont calculés, ils ont donc un rôle de valeurs, au même titre que les nombres et les chaînes de caractères par exemple.

En particulier, les valeurs booléennes étant des valeurs comme les autres, il est possible de les stocker en les affectant à des variables. Même dans les langages qui ne disposent pas d'un type spécifique pour les booléens (comme Scratch ou bien C), utiliser une représentation, comme un entier 0 ou 1, permet de stocker une information de nature booléenne. On désigne parfois sous le nom de variable booléenne une variable utilisée pour stocker une information booléenne, qu'elle utilise ou pas un type spécifique aux booléens.

Parmi les différents rôles qu'on peut attribuer à une variable booléenne (voir par exemple Sajaniemi (2002)), on rencontre parfois le terme *drapeau*, calqué de l'anglais *flag*, pour désigner une telle variable, en particulier lorsqu'elle sert à indiquer si une condition a été satisfaite ou pas. Un cas classique consiste par exemple à utiliser une variable *est_trouve* dont la valeur de vérité indique si un élément donné a été rencontré lors du parcours d'un tableau.

La section 6 précise plus en détail et illustre différents rôles que peuvent jouer les variables booléennes.

Comme on le verra dans la suite, le fait que les booléens soient des valeurs comme les autres, qui peuvent être stockées, calculées, combinées par des opérateurs spécifiques, mais aussi prises comme valeurs d'entrée et renvoyées comme valeurs de sortie dans des fonctions, explique à la fois la puissance cette notion et les difficultés conceptuelles qui s'y rapportent, notamment à cause du rôle particulier que jouent les booléens dans les structures de contrôle.

# 4. Structures de contrôle

Le but de cette partie est de mettre en évidence les différentes utilisations fondamentales des booléens dans les structures de contrôle et de décrire les différents cas d'usage pour les variables booléennes.

## 4.1. Structures conditionnelles

### *Structure conditionnelle simple*

Une des premières rencontres des booléens en programmation est leur usage en tant que condition dans des branchements conditionnels. Les structures « si ... alors ... » et « si ... alors ... sinon ... » se retrouvent en effet dans la plupart des langages.

Voici un exemple classique d'exécution conditionnelle en Python :

```python
s = input("Entrer un nombre entier")
n = int(s)
if n < 0:
    print("Le nombre entré est strictement négatif")
```

Le message à la dernière ligne du programme ne sera affiché que si le nombre *n* est strictement négatif. La version suivante présente une alternative :
```python
s = input("Entrer un nombre entier")
n = int(s)
if n < 0:
    print("Le nombre entré est strictement négatif")
else:
    print("Le nombre entré est positif ou nul")
```

Le booléen apparaît donc avec l'expression `n < 0` qui est une simple comparaison numérique. À l'exécution, cette expression sera évaluée à `vrai` ou `faux` selon la valeur reçue par `n` pour déterminer quelle branche emprunter. Ici, le premier message sera affiché si la condition `n < 0` est satisfaite, dans le cas contraire c'est le second message qui sera affiché.

Notons que le bloc d'instructions introduit par le mot-clé `else` correspond à la négation de la condition introduite par le `if` (c'est à dire ici `n >= 0`). On peut rendre cette sémantique explicite sous la forme d'un commentaire portant sur le bloc `else`, par exemple :

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
else:
    # n >= 0
    print("Le nombre entré est positif ou nul")
```

**Attention**, la structure `if condition: ... else: ... n'est cependant pas équivalente en général à la structure if condition: ... suivie de if not condition: ...`. En effet, dans ce dernier cas, si condition vaut `vrai`, le bloc d'instructions du premier `if s'exécute et est susceptible de modifier les valeurs des variables, comme dans l'exemple suivant :

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
n = -n
else:
    print("Le nombre entré est positif ou nul")
```

Lors de l'exécution du programme ci-dessus, le message affiché est correct quel que soit le nombre entré alors que dans l'exemple suivant, si le nombre `n` est strictement négatif, la valeur de `n` est remplacée par son opposé et les deux messages sont affichés.

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
n = -n
if not (n < 0):
    print("Le nombre entré est positif ou nul")
```

## Structures conditionnelles successives

Une succession d'instructions conditionnelles « si ... alors ... » sans « sinon ... » est utile pour effectuer une série d'actions « indépendantes ». Supposons qu'on dispose de trois variables entières heures, minutes et secondes représentant une durée. Le programme suivant en Python ou le programme en Scratch construisent dans `msg` un texte décrivant cette durée, en incluant les unités mais en omettant les quantités nulles :
```python
s = input("Entrer un nombre entier")
n = int(s)
if n < 0:
    print("Le nombre entré est strictement négatif")
else:
    print("Le nombre entré est positif ou nul")
```

Le booléen apparaît donc avec l'expression `n < 0` qui est une simple comparaison numérique. À l'exécution, cette expression sera évaluée à `vrai` ou `faux` selon la valeur reçue par `n` pour déterminer quelle branche emprunter. Ici, le premier message sera affiché si la condition `n < 0` est satisfaite, dans le cas contraire c'est le second message qui sera affiché.

Notons que le bloc d'instructions introduit par le mot-clé `else` correspond à la négation de la condition introduite par le `if` (c'est à dire ici `n >= 0`). On peut rendre cette sémantique explicite sous la forme d'un commentaire portant sur le bloc `else`, par exemple :

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
else:
    # n >= 0
    print("Le nombre entré est positif ou nul")
```

**Attention**, la structure `if condition: ... else: ... n'est cependant pas équivalente en général à la structure if condition: ... suivie de if not condition: ...`. En effet, dans ce dernier cas, si condition vaut `vrai`, le bloc d'instructions du premier `if s'exécute et est susceptible de modifier les valeurs des variables, comme dans l'exemple suivant :

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
n = -n
else:
    print("Le nombre entré est positif ou nul")
```

Lors de l'exécution du programme ci-dessus, le message affiché est correct quel que soit le nombre entré alors que dans l'exemple suivant, si le nombre `n` est strictement négatif, la valeur de `n` est remplacée par son opposé et les deux messages sont affichés.

```python
if n < 0:
    print("Le nombre entré est strictement négatif")
n = -n
if not (n < 0):
    print("Le nombre entré est positif ou nul")
```

## Structures conditionnelles successives

Une succession d'instructions conditionnelles « si ... alors ... » sans « sinon ... » est utile pour effectuer une série d'actions « indépendantes ». Supposons qu'on dispose de trois variables entières heures, minutes et secondes représentant une durée. Le programme suivant en Python ou le programme en Scratch construisent dans `msg` un texte décrivant cette durée, en incluant les unités mais en omettant les quantités nulles :
```python
msg = ""
if heures != 0:
    msg = msg + str(heures) + " h "
if minutes != 0:
    msg = msg + str(minutes) + " min "
if secondes != 0:
    msg = msg + str(secondes) + " s "
```

<figure><img src="image_6.png" /></figure>

Par exemple si heures, minutes et secondes désignent respectivement les valeurs 2, 0 et 15, la valeur de msg à la fin de l'exécution du programme est '2 h 15 s'.

Dans ce second exemple, on s'intéresse à la recherche de la plus grande valeur parmi les trois données. L'ordre d'exécution des deux blocs if est indifférent, et il serait incorrect de les lier par elif ou par else :

```python
def maximum(a, b, c):
    """Renvoie la plus grande des trois valeurs a, b, c."""
    mon_max = a
    if b > mon_max:
        mon_max = b
    if c > mon_max:
        mon_max = c
    return mon_max
```

Le troisième et dernier exemple illustre la difficulté à traduire un algorithme en langue naturelle en un programme contenant des branchements conditionnels :

## Énoncé

Programmer en Python l'algorithme suivant :

Soit x un nombre,
Si x est inférieur à 1 alors afficher « valeur plus petite que 1 »

Si x est compris entre 1 et 2 alors afficher « valeur comprise entre 1 et 2 »

Sinon afficher « valeur plus grande que 2 »

Une traduction littérale de cet énoncé, dont la formulation est ambiguë (« si... si... sinon »), peut mener à l'écriture de la fonction incorrecte suivante :
```python
msg = ""
if heures != 0:
    msg = msg + str(heures) + " h "
if minutes != 0:
    msg = msg + str(minutes) + " min "
if secondes != 0:
    msg = msg + str(secondes) + " s "
```

<figure><img src="image_6.png" /></figure>

Par exemple si heures, minutes et secondes désignent respectivement les valeurs 2, 0 et 15, la valeur de msg à la fin de l'exécution du programme est '2 h 15 s'.

Dans ce second exemple, on s'intéresse à la recherche de la plus grande valeur parmi les trois données. L'ordre d'exécution des deux blocs if est indifférent, et il serait incorrect de les lier par elif ou par else :

```python
def maximum(a, b, c):
    """Renvoie la plus grande des trois valeurs a, b, c."""
    mon_max = a
    if b > mon_max:
        mon_max = b
    if c > mon_max:
        mon_max = c
    return mon_max
```

Le troisième et dernier exemple illustre la difficulté à traduire un algorithme en langue naturelle en un programme contenant des branchements conditionnels :

## Énoncé

Programmer en Python l'algorithme suivant :

Soit x un nombre,
Si x est inférieur à 1 alors afficher « valeur plus petite que 1 »

Si x est compris entre 1 et 2 alors afficher « valeur comprise entre 1 et 2 »

Sinon afficher « valeur plus grande que 2 »

Une traduction littérale de cet énoncé, dont la formulation est ambiguë (« si... si... sinon »), peut mener à l'écriture de la fonction incorrecte suivante :
```python
if x < 1:
    print('valeur plus petite que 1')

if 1 <= x and x <= 2:
    print('valeur comprise entre 1 et 2')

else:
    print('valeur plus grande que 2')
```

Quand x a la valeur 0 par exemple, on obtient l'affichage incorrect :

```
valeur plus petite que 1
valeur plus grande que 2
```

Ce programme est incorrect pour toutes les valeurs de x strictement plus petites que 1. Ceci est dû au fait que le bloc `else` ne se rapporte qu'au deuxième `if`: il correspond donc à la condition `not (1 <= x and x <= 2)`, qui équivaut à `1 > x or x > 2`. On peut corriger le programme ainsi :

```python
if x < 1:
    print('valeur plus petite que 1')

if 1 <= x and x <= 2:
    print('valeur comprise entre 1 et 2')

if x > 2:
    print('valeur plus grande que 2')
```

Ce programme est correct, mais il présente encore plusieurs inconvénients : chaque comparaison effectuée est redondante, et il peut être difficile de déterminer l'expression booléenne à utiliser comme condition du dernier bloc `if`. On peut y remédier à l'aide de structures conditionnelles imbriquées, comme nous le verrons au paragraphe suivant.

## Structures conditionnelles imbriquées

Chacune des branches d'une structure conditionnelle peut à son tour contenir des tests et des branchements, on parle alors de structures conditionnelles imbriquées. Cela est souvent nécessaire mais peut rendre plus complexe l'analyse du flot d'exécution du programme.

Reprenons le dernier exemple de la section précédente. Comme les cas à considérer sont mutuellement exclusifs, il peut être préférable ici d'utiliser des structures conditionnelles imbriquées :

```python
if x < 1:
    print('valeur plus petite que 1')
else:
    # x >= 1
    if x <= 2:
        print('valeur comprise entre 1 et 2')
    else:
        # x > 2
        print('valeur plus grande que 2')
```

En Python, il existe une syntaxe plus concise (utilisant le mot-clé `elif`) permettant d'éviter une trop grande indentation du code :
```python
if x < 1:
    print('valeur plus petite que 1')

if 1 <= x and x <= 2:
    print('valeur comprise entre 1 et 2')

else:
    print('valeur plus grande que 2')
```

Quand x a la valeur 0 par exemple, on obtient l'affichage incorrect :

```
valeur plus petite que 1
valeur plus grande que 2
```

Ce programme est incorrect pour toutes les valeurs de x strictement plus petites que 1. Ceci est dû au fait que le bloc `else` ne se rapporte qu'au deuxième `if`: il correspond donc à la condition `not (1 <= x and x <= 2)`, qui équivaut à `1 > x or x > 2`. On peut corriger le programme ainsi :

```python
if x < 1:
    print('valeur plus petite que 1')

if 1 <= x and x <= 2:
    print('valeur comprise entre 1 et 2')

if x > 2:
    print('valeur plus grande que 2')
```

Ce programme est correct, mais il présente encore plusieurs inconvénients : chaque comparaison effectuée est redondante, et il peut être difficile de déterminer l'expression booléenne à utiliser comme condition du dernier bloc `if`. On peut y remédier à l'aide de structures conditionnelles imbriquées, comme nous le verrons au paragraphe suivant.

## Structures conditionnelles imbriquées

Chacune des branches d'une structure conditionnelle peut à son tour contenir des tests et des branchements, on parle alors de structures conditionnelles imbriquées. Cela est souvent nécessaire mais peut rendre plus complexe l'analyse du flot d'exécution du programme.

Reprenons le dernier exemple de la section précédente. Comme les cas à considérer sont mutuellement exclusifs, il peut être préférable ici d'utiliser des structures conditionnelles imbriquées :

```python
if x < 1:
    print('valeur plus petite que 1')
else:
    # x >= 1
    if x <= 2:
        print('valeur comprise entre 1 et 2')
    else:
        # x > 2
        print('valeur plus grande que 2')
```

En Python, il existe une syntaxe plus concise (utilisant le mot-clé `elif`) permettant d'éviter une trop grande indentation du code :
```python
if x < 1:
    print('valeur plus petite que 1')
elif x <= 2:
    print('valeur comprise entre 1 et 2')
else:
    print('valeur plus grande que 2')
```

On note qu'il n'existe pas en Scratch de construction équivalente au `elif`. La représentation par blocs rend très visible l'imbrication des conditionnelles successives.

## Instruction return dans une structure conditionnelle

La succession et l'imbrication de conditionnelles ont un comportement différent dans une fonction, notamment en présence d'instructions `return` dans les blocs du `if` et du `else`. En particulier, il est possible dans certains cas d'omettre le mot-clé `else` ou de remplacer `elif` par `if`.

Considérons par exemple les fonctions `choix1` et `choix2`, qui reçoivent en argument un nombre `n` et renvoient une chaîne indiquant si la valeur de `n` est négative ou positive et précise dans le cas positif si elle est paire ou impaire:

```python
def choix1(n):
    if n < 0:
        return 'négatif'
    if n % 2 == 0:
        return 'positif et pair'
    return 'positif et impair'

def choix2(n):
    if n < 0:
        return 'négatif'
    elif n % 2 == 0:
        return 'positif et pair'
    else:
        return 'positif et impair'
```

Ces deux fonctions ont le même comportement, car dans les deux cas quand `n` est négatif l'instruction `return 'négatif'` (ligne 3) est exécutée et le reste du corps de la fonction n'est pas pris en compte. Par contre, dans la fonction `choix1` il est bien sûr indispensable de choisir correctement l'ordre des conditions à tester (les intervertir ne serait pas équivalent).

La question de la version à privilégier n'a pas de réponse claire et relève de la préférence et du style. On peut avancer que la fonction `choix2` est plus explicite pour un programmeur débutant car elle souligne le fait que le second bloc n'est exécuté que si la première condition est fausse.

Un autre style de programmation possible est d'utiliser une variable `resultat` dont seule la dernière valeur sera renvoyée par une unique instruction `return` en fin de fonction, comme illustré par la fonction `choix3` ci-dessous :

```python
def choix3(n):
    if n < 0:
        resultat = 'négatif'
elif n % 2 == 0:
```
```python
if x < 1:
    print('valeur plus petite que 1')
elif x <= 2:
    print('valeur comprise entre 1 et 2')
else:
    print('valeur plus grande que 2')
```

On note qu'il n'existe pas en Scratch de construction équivalente au `elif`. La représentation par blocs rend très visible l'imbrication des conditionnelles successives.

## Instruction return dans une structure conditionnelle

La succession et l'imbrication de conditionnelles ont un comportement différent dans une fonction, notamment en présence d'instructions `return` dans les blocs du `if` et du `else`. En particulier, il est possible dans certains cas d'omettre le mot-clé `else` ou de remplacer `elif` par `if`.

Considérons par exemple les fonctions `choix1` et `choix2`, qui reçoivent en argument un nombre `n` et renvoient une chaîne indiquant si la valeur de `n` est négative ou positive et précise dans le cas positif si elle est paire ou impaire:

```python
def choix1(n):
    if n < 0:
        return 'négatif'
    if n % 2 == 0:
        return 'positif et pair'
    return 'positif et impair'

def choix2(n):
    if n < 0:
        return 'négatif'
    elif n % 2 == 0:
        return 'positif et pair'
    else:
        return 'positif et impair'
```

Ces deux fonctions ont le même comportement, car dans les deux cas quand `n` est négatif l'instruction `return 'négatif'` (ligne 3) est exécutée et le reste du corps de la fonction n'est pas pris en compte. Par contre, dans la fonction `choix1` il est bien sûr indispensable de choisir correctement l'ordre des conditions à tester (les intervertir ne serait pas équivalent).

La question de la version à privilégier n'a pas de réponse claire et relève de la préférence et du style. On peut avancer que la fonction `choix2` est plus explicite pour un programmeur débutant car elle souligne le fait que le second bloc n'est exécuté que si la première condition est fausse.

Un autre style de programmation possible est d'utiliser une variable `resultat` dont seule la dernière valeur sera renvoyée par une unique instruction `return` en fin de fonction, comme illustré par la fonction `choix3` ci-dessous :

```python
def choix3(n):
    if n < 0:
        resultat = 'négatif'
elif n % 2 == 0:
```
```python
resultat = 'positif et pair'
else:
    resultat = 'positif et impair'
return resultat
```

Dans ce cas, il est indispensable de spécifier entièrement la structure conditionnelle, en incluant tous les mots-clés `elif` et `else` permettant de distinguer les différents cas.

## 4.2. Boucle « tant que » et ses variantes

Pour répéter un ensemble d'instructions dont l'exécution ou l'arrêt dépend d'une certaine condition, la plupart des langages proposent des structures du type « tant que *condition* faire instructions ». Elle peuvent prendre plusieurs formes selon les langages.

### Itération avec condition au début

Tous les langages de programmation impératifs fournissent une structure de boucle où la condition est formulée au début. Sous cette forme, la condition est d'abord évaluée, et en fonction du booléen obtenu, soit l'itération est interrompue, soit le corps de la boucle est exécuté une fois puis la boucle recommence.

Ainsi, en Python, on pourra écrire :

```python
p = 1
while p <= n:
    p = 2 * p
```

et ce morceau de programme se terminera dès que la valeur de p devient strictement supérieure à celle de n. Si la condition de boucle est fausse dès le début, par exemple ici si n vaut 0, le corps de la boucle n'est pas exécuté du tout.

En Scratch, la structure analogue s'écrit « répéter jusqu'à ce que *condition* » ce qui signifie que l'itération est interrompue dès que la condition est vraie. Autrement dit, le corps de la boucle sera exécuté tant que la condition est fausse. Le programme précédent s'écrirait donc :

<figure><img src="image_7.png" /></figure>

Les deux structures sont équivalentes, la différence ne porte que sur la formulation de la condition, puisque la première forme donne une condition de continuation alors que la seconde donne une condition d'arrêt, or l'une est la négation de l'autre par définition.

Les élèves de collège rencontrent donc d'abord, avec Scratch, des boucles dont la condition est une condition d'arrêt. En seconde, ils découvrent le langage Python où les boucles s'écrivent avec une condition de continuation. Dans les deux cas le corps de la boucle peut ne jamais être exécuté. C'est la négation qui permet de passer de la condition d'arrêt à la condition de continuation. Sur ce point, la transition d'un langage à l'autre doit donc être abordée avec soin, car on sait que la négation logique est une notion difficile.

Notons que Scratch dépasse le cadre de la programmation impérative classique et contient des aspects de programmation événementielle et concurrente, notamment la notion d'événement (introduit typiquement par le mot « Quand... ») qui se distingue de celle de condition (« Si... »). Ces deux notions ne sont cependant pas toujours faciles à distinguer : Scratch dispose par exemple :
```python
resultat = 'positif et pair'
else:
    resultat = 'positif et impair'
return resultat
```

Dans ce cas, il est indispensable de spécifier entièrement la structure conditionnelle, en incluant tous les mots-clés `elif` et `else` permettant de distinguer les différents cas.

## 4.2. Boucle « tant que » et ses variantes

Pour répéter un ensemble d'instructions dont l'exécution ou l'arrêt dépend d'une certaine condition, la plupart des langages proposent des structures du type « tant que *condition* faire instructions ». Elle peuvent prendre plusieurs formes selon les langages.

### Itération avec condition au début

Tous les langages de programmation impératifs fournissent une structure de boucle où la condition est formulée au début. Sous cette forme, la condition est d'abord évaluée, et en fonction du booléen obtenu, soit l'itération est interrompue, soit le corps de la boucle est exécuté une fois puis la boucle recommence.

Ainsi, en Python, on pourra écrire :

```python
p = 1
while p <= n:
    p = 2 * p
```

et ce morceau de programme se terminera dès que la valeur de p devient strictement supérieure à celle de n. Si la condition de boucle est fausse dès le début, par exemple ici si n vaut 0, le corps de la boucle n'est pas exécuté du tout.

En Scratch, la structure analogue s'écrit « répéter jusqu'à ce que *condition* » ce qui signifie que l'itération est interrompue dès que la condition est vraie. Autrement dit, le corps de la boucle sera exécuté tant que la condition est fausse. Le programme précédent s'écrirait donc :

<figure><img src="image_7.png" /></figure>

Les deux structures sont équivalentes, la différence ne porte que sur la formulation de la condition, puisque la première forme donne une condition de continuation alors que la seconde donne une condition d'arrêt, or l'une est la négation de l'autre par définition.

Les élèves de collège rencontrent donc d'abord, avec Scratch, des boucles dont la condition est une condition d'arrêt. En seconde, ils découvrent le langage Python où les boucles s'écrivent avec une condition de continuation. Dans les deux cas le corps de la boucle peut ne jamais être exécuté. C'est la négation qui permet de passer de la condition d'arrêt à la condition de continuation. Sur ce point, la transition d'un langage à l'autre doit donc être abordée avec soin, car on sait que la négation logique est une notion difficile.

Notons que Scratch dépasse le cadre de la programmation impérative classique et contient des aspects de programmation événementielle et concurrente, notamment la notion d'événement (introduit typiquement par le mot « Quand... ») qui se distingue de celle de condition (« Si... »). Ces deux notions ne sont cependant pas toujours faciles à distinguer : Scratch dispose par exemple :
d'une instruction « attendre jusqu'à » acceptant n'importe quelle expression booléenne, qui bloque
le script qui la contient tant que la condition n'est pas vérifiée. Elle est en fait équivalente à une
boucle « répéter jusqu'à » portant sur la même condition et dont le corps ne contiendrait aucune
instruction. La condition peut donc être vue comme condition d'arrêt pour la boucle d'attente. Nous
n'entrerons pas plus dans le détail sur ces questions.

## Itération avec condition à la fin

Certains algorithmes s'expriment plus naturellement avec une forme d'itération où le corps de la boucle est toujours exécuté au moins une fois et où la condition d'arrêt ou de continuation est testée après.

Par exemple, pour demander à l'utilisateur un nombre entier positif ou nul, on voudra suivre la procédure suivante:

répéter

* dire « Entrer un nombre entier positif ou nul »

* saisir un entier n

jusqu'à ce que $n \ge 0$

Comme cette structure est utile, certains langages proposent un type de structure où la condition suit le corps de la boucle. C'est le cas par exemple en C (et dans les langages qui en dérivent comme C++, C#, Java, etc.) avec la structure `do ... while (condition);` ou en Pascal avec la structure `repeat ... until`. Ainsi, l'exemple suivant en Pascal assure la saisie d'un entier positif ou nul :

```pascal
repeat
  writeln('Entrer un nombre entier positif ou nul');
  readln(s); (* lit une chaîne de caractères dans s *)
  val(s, n, err); (* convertit s en entier dans n,
                    rend err non nul en cas d'erreur *)
until err = 0 and n >= 0;
```

Python et Scratch ne disposent pas d'une structure de ce type. Dans ces langages, il est donc nécessaire de se baser sur la boucle avec condition au début pour représenter ce mécanisme. Une façon simple est d'utiliser une boucle `while` en la faisant précéder d'une première exécution du corps de la boucle :

```python
print("Entrer un nombre entier positif ou nul")
s = input()
n = int(s)
while n < 0:
    print("Entrer un nombre entier positif ou nul")
    s = input()
    n = int(s)
```

L'inconvénient de cette approche est qu'elle impose de répéter une partie du code, ce qui est généralement considéré comme une mauvaise pratique. Il est parfois possible d'éviter la répétition en choisissant judicieusement les valeurs initiales des variables :
d'une instruction « attendre jusqu'à » acceptant n'importe quelle expression booléenne, qui bloque
le script qui la contient tant que la condition n'est pas vérifiée. Elle est en fait équivalente à une
boucle « répéter jusqu'à » portant sur la même condition et dont le corps ne contiendrait aucune
instruction. La condition peut donc être vue comme condition d'arrêt pour la boucle d'attente. Nous
n'entrerons pas plus dans le détail sur ces questions.

## Itération avec condition à la fin

Certains algorithmes s'expriment plus naturellement avec une forme d'itération où le corps de la boucle est toujours exécuté au moins une fois et où la condition d'arrêt ou de continuation est testée après.

Par exemple, pour demander à l'utilisateur un nombre entier positif ou nul, on voudra suivre la procédure suivante:

répéter

* dire « Entrer un nombre entier positif ou nul »

* saisir un entier n

jusqu'à ce que $n \ge 0$

Comme cette structure est utile, certains langages proposent un type de structure où la condition suit le corps de la boucle. C'est le cas par exemple en C (et dans les langages qui en dérivent comme C++, C#, Java, etc.) avec la structure `do ... while (condition);` ou en Pascal avec la structure `repeat ... until`. Ainsi, l'exemple suivant en Pascal assure la saisie d'un entier positif ou nul :

```pascal
repeat
    writeln('Entrer un nombre entier positif ou nul');
    readln(s); (* lit une chaîne de caractères dans s *)
    val(s, n, err); (* convertit s en entier dans n,
                       rend err non nul en cas d'erreur *)
until err = 0 and n >= 0;
```

Python et Scratch ne disposent pas d'une structure de ce type. Dans ces langages, il est donc nécessaire de se baser sur la boucle avec condition au début pour représenter ce mécanisme. Une façon simple est d'utiliser une boucle `while` en la faisant précéder d'une première exécution du corps de la boucle :

```python
print("Entrer un nombre entier positif ou nul")
s = input()
n = int(s)
while n < 0:
    print("Entrer un nombre entier positif ou nul")
    s = input()
    n = int(s)
```

L'inconvénient de cette approche est qu'elle impose de répéter une partie du code, ce qui est généralement considéré comme une mauvaise pratique. Il est parfois possible d'éviter la répétition en choisissant judicieusement les valeurs initiales des variables :
```python
n = -1
while n < 0:
    print("Entrer un nombre entier positif ou nul")
    s = input()
    n = int(s)
```

Cette façon de procéder n'est pas toujours applicable.

## Itération infinie et interruption d'itération

L'itération avec condition est la forme la plus générale d'itération, elle permet de représenter les autres formes de boucles plus spécialisées comme l'itération numérique « pour *i* de 1 à *n* ». La contrepartie de cette expressivité est qu'elle permet d'écrire des programmes dont l'exécution ne termine pas. En effet, il est possible en général que la condition d'arrêt de soit jamais remplie.

Le fait de ne pas terminer peut parfois être souhaité, c'est le cas par exemple dans les programmes interactifs et plus généralement quand on suit un paradigme événementiel. Ainsi, Scratch propose une structure « répéter indéfiniment ». Pour faire la même chose dans un langage qui ne propose pas de structure analogue, on emploie une itération avec une condition de continuation toujours vraie, c'est-à-dire en écrivant `while True`.

Certains langages de programmation proposent par ailleurs un moyen d'interrompre une boucle en cours d'itération, indépendamment de la condition d'arrêt formulée sur la boucle. C'est le cas de Python et de C avec l'instruction `break`, ainsi que l'instruction `return` qui termine l'exécution d'une fonction en renvoyant une valeur.

La question de savoir si l'usage des instructions `break` ou `return` dans le corps d'une boucle facilite l'écriture d'un programme, ou s'il rend sa compréhension et son éventuelle preuve plus difficiles (puisqu'elle peut compliquer la condition d'arrêt et la formulation d'un invariant de boucle) ne semble pas faire consensus dans la communauté. Ce débat déjà ancien, dont l'origine est souvent attribuée à une lettre de Dijkstra (1968), a fait l'objet de nombreuses publications et commentaires (voir par exemple Roberts (1995), Ben-Ari (1996) et Barnes & Shinners-Kennedy (2011) qui présentent des arguments en faveur de la sortie anticipée de boucle).

Une question emblématique identifiée par ces articles est celle dite de la « boucle et demie », qui s'illustre de manière simple dans le problème de recherche de la première occurrence d'une valeur donnée dans une liste. Barnes & Shinners-Kennedy (2011) proposent trois versions d'une fonction en C résolvant ce problème, dont nous présentons une transcription en Python ci-dessous. La première version de la fonction utilise une condition composée dans la boucle `while` et n'utilise pas de sortie anticipée de la boucle :

```python
def premiere Occurrence(liste, x):
    i = 0
    while i < len(liste) and liste[i] != x:
        i += 1
        if i < len(liste):
            return i
        else:
            return None
```

Il est nécessaire ici de déterminer, en sortie de boucle, si celle-ci s'est interrompue sur un cas d'échec (élément *x* absent de *liste*) ou sur un cas de réussite. Dans la version suivante, la boucle semble prévue pour parcourir la liste en entier, mais une condition dans le corps permet de
```python
n = -1
while n < 0:
    print("Entrer un nombre entier positif ou nul")
    s = input()
    n = int(s)
```

Cette façon de procéder n'est pas toujours applicable.

## Itération infinie et interruption d'itération

L'itération avec condition est la forme la plus générale d'itération, elle permet de représenter les autres formes de boucles plus spécialisées comme l'itération numérique « pour *i* de 1 à *n* ». La contrepartie de cette expressivité est qu'elle permet d'écrire des programmes dont l'exécution ne termine pas. En effet, il est possible en général que la condition d'arrêt de soit jamais remplie.

Le fait de ne pas terminer peut parfois être souhaité, c'est le cas par exemple dans les programmes interactifs et plus généralement quand on suit un paradigme événementiel. Ainsi, Scratch propose une structure « répéter indéfiniment ». Pour faire la même chose dans un langage qui ne propose pas de structure analogue, on emploie une itération avec une condition de continuation toujours vraie, c'est-à-dire en écrivant `while True`.

Certains langages de programmation proposent par ailleurs un moyen d'interrompre une boucle en cours d'itération, indépendamment de la condition d'arrêt formulée sur la boucle. C'est le cas de Python et de C avec l'instruction `break`, ainsi que l'instruction `return` qui termine l'exécution d'une fonction en renvoyant une valeur.

La question de savoir si l'usage des instructions `break` ou `return` dans le corps d'une boucle facilite l'écriture d'un programme, ou s'il rend sa compréhension et son éventuelle preuve plus difficiles (puisqu'elle peut compliquer la condition d'arrêt et la formulation d'un invariant de boucle) ne semble pas faire consensus dans la communauté. Ce débat déjà ancien, dont l'origine est souvent attribuée à une lettre de Dijkstra (1968), a fait l'objet de nombreuses publications et commentaires (voir par exemple Roberts (1995), Ben-Ari (1996) et Barnes & Shinners-Kennedy (2011) qui présentent des arguments en faveur de la sortie anticipée de boucle).

Une question emblématique identifiée par ces articles est celle dite de la « boucle et demie », qui s'illustre de manière simple dans le problème de recherche de la première occurrence d'une valeur donnée dans une liste. Barnes & Shinners-Kennedy (2011) proposent trois versions d'une fonction en C résolvant ce problème, dont nous présentons une transcription en Python ci-dessous. La première version de la fonction utilise une condition composée dans la boucle `while` et n'utilise pas de sortie anticipée de la boucle :

```python
def premiere Occurrence(liste, x):
    i = 0
    while i < len(liste) and liste[i] != x:
        i += 1
        if i < len(liste):
            return i
        else:
            return None
```

Il est nécessaire ici de déterminer, en sortie de boucle, si celle-ci s'est interrompue sur un cas d'échec (élément *x* absent de *liste*) ou sur un cas de réussite. Dans la version suivante, la boucle semble prévue pour parcourir la liste en entier, mais une condition dans le corps permet de
l'interrompre avec break en cas de réussite. Cependant, il est toujours nécessaire de déterminer
quel cas a provoqué la sortie.

```python
def premiere Occurrence(liste, x):
    i = 0
    while i < len(liste):
        if liste[i] == x:
            break
        i += 1
    if i < len(liste):
        return i
    else:
        return None
```

Dans la dernière version ci-dessous, une instruction return est exécutée en cas de réussite en cours de boucle. La sortie de boucle « normale » correspond donc uniquement à un cas d'échec<fnref n="5" />.

```python
def premiereOccurrence(liste, x):
    i = 0
    while i < len(liste):
        if liste[i] == x:
            return i
        i += 1
    return None
```

Il nous semble que cet exemple illustre bien la difficulté possible à s'imposer une discipline de programmation interdisant les interruptions anticipées de boucles. La première version ci-dessus par exemple nécessite d'écrire une condition de boucle plus difficile. D'autre part, il n'est pas évident de déterminer dans quels cas un second test en sortie de boucle est nécessaire. Enfin, s'il est en général aisé de traduire la boucle while en boucle for dans les versions 2 et 3, c'est impossible pour la version 1 dans certains langages (notamment Python).

Remarquons qu'il est possible d'écrire une version alternative du programme du sujet de bac NSI mentionné au paragraphe 2.2 utilisant plusieurs sorties anticipées de boucle :

```python
def recherche(gene, seq_adn):
    n = len(seq_adn)
    g = len(gene)
    for i in range(n):
        j = 0
        while j < g:
            if gene[j] != seq_adn[i+j]:
                break
            j += 1
        if j == g:
            return True
    return False
```

<footnote n="5">⁵Dans cette dernière version de la fonction, il serait bien sûr incorrect de compléter l'instruction conditionnelle dans la boucle d'un `else: return None`, ce qui est une erreur d'élève fréquente.</footnote>
l'interrompre avec break en cas de réussite. Cependant, il est toujours nécessaire de déterminer
quel cas a provoqué la sortie.

```python
def premiere Occurrence(liste, x):
    i = 0
    while i < len(liste):
        if liste[i] == x:
            break
        i += 1
    if i < len(liste):
        return i
    else:
        return None
```

Dans la dernière version ci-dessous, une instruction return est exécutée en cas de réussite en cours de boucle. La sortie de boucle « normale » correspond donc uniquement à un cas d'échec<fnref n="5" />.

```python
def premiere Occurrence(liste, x):
    i = 0
    while i < len(liste):
        if liste[i] == x:
            return i
        i += 1
    return None
```

Il nous semble que cet exemple illustre bien la difficulté possible à s'imposer une discipline de programmation interdisant les interruptions anticipées de boucles. La première version ci-dessus par exemple nécessite d'écrire une condition de boucle plus difficile. D'autre part, il n'est pas évident de déterminer dans quels cas un second test en sortie de boucle est nécessaire. Enfin, s'il est en général aisé de traduire la boucle while en boucle for dans les versions 2 et 3, c'est impossible pour la version 1 dans certains langages (notamment Python).

Remarquons qu'il est possible d'écrire une version alternative du programme du sujet de bac NSI mentionné au paragraphe 2.2 utilisant plusieurs sorties anticipées de boucle :

```python
def recherche(gene, seq_adn):
    n = len(seq_adn)
    g = len(gene)
    for i in range(n):
        j = 0
        while j < g:
            if gene[j] != seq_adn[i+j]:
                break
            j += 1
        if j == g:
            return True
    return False
```

<footnote n="5">⁵Dans cette dernière version de la fonction, il serait bien sûr incorrect de compléter l'instruction conditionnelle dans la boucle d'un `else: return None`, ce qui est une erreur d'élève fréquente.</footnote>
Il reste que le test if j == g, qui permet de déterminer la raison de l'arrêt de la boucle interne, est subtil et susceptible de présenter une difficulté.

Les trois articles précédemment cités contiennent plusieurs autres exemples relatifs à cette question, illustrés dans divers langages.

# 5. Expressions booléennes

Dans cette partie, on étudie les principales façons de calculer des booléens. Même s'il est possible d'utiliser les littéraux<fnref n="6" /> représentant des booléens, ces valeurs sont en général calculées à partir d'autres valeurs au moyen d'opérateurs ou de fonctions. On parle alors d'expressions (à valeurs) booléennes.

L'objectif n'est pas de chercher l'exhaustivité par rapport à un langage donné mais distinguer les différents ingrédients qui contribuent à l'écriture de telles expressions.

## 5.1. Produire des booléens : comparaisons et autres relations

Dans tous les langages de programmation, des opérateurs permettent de comparer deux valeurs (égalité, inégalité ou ordre) et de produire une valeur booléenne en conséquence.

### Comparaisons en Scratch

Le langage Scratch ne propose que les opérateurs d'égalité et d'ordre strict, ce qui oblige à utiliser des opérateurs booléens pour former d'autres comparaisons. Il peut être intéressant de donner du sens au collège à la relation $\le$ à l'aide de l'opérateur booléen « ou », ou comme négation de la relation $>$ :

<figure><img src="image_8.png" /></figure>

### Comparaisons en Python

En Python, six opérateurs permettent de comparer des valeurs de type quelconque et s'évaluent en une valeur booléenne. Deux concernent l'égalité :

* l'expression $a == b$ est vraie lorsque les valeurs de a et b sont égales ;

* l'expression $a != b$ est vraie lorsque les valeurs de a et b sont différentes.

Il est important de ne pas confondre l'opérateur d'égalité $==$ avec l'affectation notée $=$. Il s'agit d'une erreur très fréquente chez les programmeurs débutants<fnref n="7" />. Il existe aussi un opérateur permettant de tester l'identité en mémoire de deux valeurs, que nous mentionnons brièvement plus loin.

```python
>>> 2 + 2 == 4
True
>>> 'bonjour'[3:] == 'nuit'
False
>>> [1, 3, 5] != [8, 7]
```

<footnote n="6">⁶En programmation, un littéral, ou valeur littérale, est une valeur écrite explicitement dans un programme. En Python, on écrit par exemple `3` pour désigner la valeur entière 3, `'a'` pour désigner le caractère a, ou `False` pour désigner la valeur booléenne faux (Commission Inter-Irem Informatique (C3I), 2020).</footnote>

<footnote n="7">⁷En Python, l'interpréteur signale dans ce cas une erreur car une affectation n'est pas considérée comme une expression. En C, cette utilisation est parfois signalée par le compilateur sous forme d'avertissement car cette écriture est légale et peut être voulue.</footnote>
Il reste que le test if j == g, qui permet de déterminer la raison de l'arrêt de la boucle interne, est subtil et susceptible de présenter une difficulté.

Les trois articles précédemment cités contiennent plusieurs autres exemples relatifs à cette question, illustrés dans divers langages.

# 5. Expressions booléennes

Dans cette partie, on étudie les principales façons de calculer des booléens. Même s'il est possible d'utiliser les littéraux<fnref n="6" /> représentant des booléens, ces valeurs sont en général calculées à partir d'autres valeurs au moyen d'opérateurs ou de fonctions. On parle alors d'expressions (à valeurs) booléennes.

L'objectif n'est pas de chercher l'exhaustivité par rapport à un langage donné mais distinguer les différents ingrédients qui contribuent à l'écriture de telles expressions.

## 5.1. Produire des booléens : comparaisons et autres relations

Dans tous les langages de programmation, des opérateurs permettent de comparer deux valeurs (égalité, inégalité ou ordre) et de produire une valeur booléenne en conséquence.

### Comparaisons en Scratch

Le langage Scratch ne propose que les opérateurs d'égalité et d'ordre strict, ce qui oblige à utiliser des opérateurs booléens pour former d'autres comparaisons. Il peut être intéressant de donner du sens au collège à la relation $\le$ à l'aide de l'opérateur booléen « ou », ou comme négation de la relation $>$ :

<figure><img src="image_8.png" /></figure>

### Comparaisons en Python

En Python, six opérateurs permettent de comparer des valeurs de type quelconque et s'évaluent en une valeur booléenne. Deux concernent l'égalité :

* l'expression $a == b$ est vraie lorsque les valeurs de a et b sont égales ;

* l'expression $a != b$ est vraie lorsque les valeurs de a et b sont différentes.

Il est important de ne pas confondre l'opérateur d'égalité $==$ avec l'affectation notée $=$. Il s'agit d'une erreur très fréquente chez les programmeurs débutants<fnref n="7" />. Il existe aussi un opérateur permettant de tester l'identité en mémoire de deux valeurs, que nous mentionnons brièvement plus loin.

```python
>>> 2 + 2 == 4
True
>>> 'bonjour'[3:] == 'nuit'
False
>>> [1, 3, 5] != [8, 7]
```

<footnote n="6">⁶En programmation, un littéral, ou valeur littérale, est une valeur écrite explicitement dans un programme. En Python, on écrit par exemple `3` pour désigner la valeur entière 3, `'a'` pour désigner le caractère a, ou `False` pour désigner la valeur booléenne faux (Commission Inter-Irem Informatique (C3I), 2020).</footnote>

<footnote n="7">⁷En Python, l'interpréteur signale dans ce cas une erreur car une affectation n'est pas considérée comme une expression. En C, cette utilisation est parfois signalée par le compilateur sous forme d'avertissement car cette écriture est légale et peut être voulue.</footnote>
True
>>> {'a': 8, 'b': [37]} == {'b': [36 + 1], 'a': 8}
True

Les quatre autres opérateurs de comparaison concernent les types pour lesquels il y a une notion d'ordre :

* a < b est vraie si la valeur de a est strictement inférieure à celle de b, fausse sinon ;

* a > b est vraie si la valeur de a est strictement supérieure à celle de b, fausse sinon ;

* a <= b et a >= b correspondent aux comparaisons larges.

>>> 3 < 8
True

## Remarques sur les comparaisons

Pour les chaînes de caractères, c'est l'ordre dit *lexicographique* qui s'applique, tant en Scratch qu'en Python : on compare les premiers caractères des deux chaînes, puis les seconds si les premiers sont égaux, et ainsi de suite.

>>> 'alphabet' < 'alphonse'
True
>>> 'un' < 'deux'
False
>>> '150' < '90'  # ce sont les caractères qui sont comparés
True

Lorsqu'on compare des chaînes de caractères, ce sont les indices de caractères (selon le standard Unicode, spécifiquement) qui servent à comparer les caractères, ce qui ne correspond pas à l'ordre alphabétique :

>>> 'Z' < 'a'  # les majuscules sont avant les minuscules
True
>>> 'z' < 'é'  # les lettres accentuées viennent après celles sans accents
True

C'est également l'ordre *lexicographique* qui permet de comparer des listes et des *n*-uplets en Python :

>>> (1, 0) > (0, 1)
True
>>> (1, 0, 1) < (1, 1)
True
>>> [1, 2, 3] > [1, 2, 3, 4]
False

Pour le type *set*, les opérateurs de comparaison désignent la relation mathématique d'inclusion (stricte ou large) :
True
>>> {'a': 8, 'b': [37]} == {'b': [36 + 1], 'a': 8}
True

Les quatre autres opérateurs de comparaison concernent les types pour lesquels il y a une notion d'ordre :

* a < b est vraie si la valeur de a est strictement inférieure à celle de b, fausse sinon ;

* a > b est vraie si la valeur de a est strictement supérieure à celle de b, fausse sinon ;

* a <= b et a >= b correspondent aux comparaisons larges.

>>> 3 < 8
True

## Remarques sur les comparaisons

Pour les chaînes de caractères, c'est l'ordre dit *lexicographique* qui s'applique, tant en Scratch qu'en Python : on compare les premiers caractères des deux chaînes, puis les seconds si les premiers sont égaux, et ainsi de suite.

>>> 'alphabet' < 'alphonse'
True
>>> 'un' < 'deux'
False
>>> '150' < '90'  # ce sont les caractères qui sont comparés
True

Lorsqu'on compare des chaînes de caractères, ce sont les indices de caractères (selon le standard Unicode, spécifiquement) qui servent à comparer les caractères, ce qui ne correspond pas à l'ordre alphabétique :

>>> 'Z' < 'a'  # les majuscules sont avant les minuscules
True
>>> 'z' < 'é'  # les lettres accentuées viennent après celles sans accents
True

C'est également l'ordre *lexicographique* qui permet de comparer des listes et des *n*-uplets en Python :

>>> (1, 0) > (0, 1)
True
>>> (1, 0, 1) < (1, 1)
True
>>> [1, 2, 3] > [1, 2, 3, 4]
False

Pour le type *set*, les opérateurs de comparaison désignent la relation mathématique d'inclusion (stricte ou large) :
```python
>>> {'a', 'b'} < {'b', 'c', 'a'}
True
>>> {'b', 'a'} <= {'a', 'b'}
True
```

Python autorise à écrire, par commodité, des comparaisons multiples comme `0 <= i < len(lst)`. Cette expression est équivalente à `0 <= i and i < len(lst)`, ce qui est une originalité de Python.

Cette facilité de syntaxe fait figure d'exception parmi les langages de programmation et peut être source d'erreur.

```python
>>> 0 < 3 > 2  # comment comprendre cette expression ?
True
```

Par ailleurs, les opérateurs de comparaison peuvent être utilisés sur des opérandes de types différents seulement dans les cas où une conversion implicite est possible et implémentée dans le langage entre les deux types, par exemple entre types numériques `int` et `float`. Dans les autres cas, les tests d'égalité considèrent en général comme différentes des valeurs de types différents, alors que les opérateurs d'ordre provoquent une erreur<fnref n="8" />.

```python
>>> 2 == 2.0
True
>>> 8.5 < 3
False
>>> 5 != 'patate'
True
>>> 5 <= 'patate'
TypeError: '<=' not supported between instances of 'int' and 'str'
```

Il est important d'être vigilant sur les conversions automatiques possibles lors de comparaisons entre nombres, qui peuvent avoir des résultats inattendus, en particulier lorsqu'elles impliquent des nombres de type `float` (mais ceci n'est pas notre propos principal, voir par exemple la brochure IREM *Algorithmique et programmation au Cycle 4* (Beffara et al., 2017)).

## Autres relations

Nous mentionnons ici deux autres relations qui nous semblent utiles dans certains contextes. Selon le langage, il peut exister d'autres opérateurs à valeur booléenne, nous ne cherchons pas ici à en dresser une liste exhaustive.

En Python, `A in B` détermine si la valeur A apparaît dans la structure de donnée B qui peut être par exemple une liste ou un dictionnaire (dans ce cas le test concerne les clés du dictionnaire). Sur les chaînes de caractères, cet opérateur<fnref n="9" /> teste l'apparition d'une chaîne comme fragment d'une autre :

<footnote n="8">⁸Les règles précises qui s'appliquent en Python pour la comparaison de valeurs de types différents sont assez subtiles, mais le détail n'est pas notre propos ici. La documentation du langage explique précisément le mécanisme. Voir par exemple https://docs.python.org/fr/3/reference/datamodel.html#object.__lt__</footnote>

<footnote n="9">⁹En programmation, on emploie le mot *opérateur* aussi pour ce que l'on appellerait en mathématiques une relation, parce qu'il calcule un résultat à partir des valeurs auxquelles il est appliqué (Commission Inter-Irem Informatique (C3I), 2020).</footnote>
```python
>>> {'a', 'b'} < {'b', 'c', 'a'}
True
>>> {'b', 'a'} <= {'a', 'b'}
True
```

Python autorise à écrire, par commodité, des comparaisons multiples comme `0 <= i < len(lst)`. Cette expression est équivalente à `0 <= i and i < len(lst)`, ce qui est une originalité de Python.

Cette facilité de syntaxe fait figure d'exception parmi les langages de programmation et peut être source d'erreur.

```python
>>> 0 < 3 > 2  # comment comprendre cette expression ?
True
```

Par ailleurs, les opérateurs de comparaison peuvent être utilisés sur des opérandes de types différents seulement dans les cas où une conversion implicite est possible et implémentée dans le langage entre les deux types, par exemple entre types numériques `int` et `float`. Dans les autres cas, les tests d'égalité considèrent en général comme différentes des valeurs de types différents, alors que les opérateurs d'ordre provoquent une erreur<fnref n="8" />.

```python
>>> 2 == 2.0
True
>>> 8.5 < 3
False
>>> 5 != 'patate'
True
>>> 5 <= 'patate'
TypeError: '<=' not supported between instances of 'int' and 'str'
```

Il est important d'être vigilant sur les conversions automatiques possibles lors de comparaisons entre nombres, qui peuvent avoir des résultats inattendus, en particulier lorsqu'elles impliquent des nombres de type `float` (mais ceci n'est pas notre propos principal, voir par exemple la brochure IREM *Algorithmique et programmation au Cycle 4* (Beffara et al., 2017)).

## Autres relations

Nous mentionnons ici deux autres relations qui nous semblent utiles dans certains contextes. Selon le langage, il peut exister d'autres opérateurs à valeur booléenne, nous ne cherchons pas ici à en dresser une liste exhaustive.

En Python, `A in B` détermine si la valeur A apparaît dans la structure de donnée B qui peut être par exemple une liste ou un dictionnaire (dans ce cas le test concerne les clés du dictionnaire). Sur les chaînes de caractères, cet opérateur<fnref n="9" /> teste l'apparition d'une chaîne comme fragment d'une autre :

<footnote n="8">⁸Les règles précises qui s'appliquent en Python pour la comparaison de valeurs de types différents sont assez subtiles, mais le détail n'est pas notre propos ici. La documentation du langage explique précisément le mécanisme. Voir par exemple https://docs.python.org/fr/3/reference/datamodel.html#object.__lt__</footnote>

<footnote n="9">⁹En programmation, on emploie le mot *opérateur* aussi pour ce que l'on appellerait en mathématiques une relation, parce qu'il calcule un résultat à partir des valeurs auxquelles il est appliqué (Commission Inter-Irem Informatique (C3I), 2020).</footnote>
>>> 'ane' in 'banane'
True

Attention : l'opérateur not ayant une priorité plus faible que celle de l'opérateur in, l'expression not A in B doit être lue comme not (A in B) et non comme (not A) in B qui n'est probablement pas le sens souhaité. Une autre syntaxe possible (et potentiellement moins ambiguë) utilise l'opérateur dédié not in, comme dans l'expression A not in B.

Ces opérateurs existent également en Scratch pour les chaînes de caractères (en vert) et pour les listes (en orange) :

<figure><img src="image_9.png" /></figure>

Notons que la présence d'un point d'interrogation dans ces opérateurs (comme la formulation en langue naturelle de la plupart des autres blocs de Scratch) est un choix des concepteurs du langage. On peut supposer qu'elle a ici pour but d'insister sur le caractère booléen de ces opérateurs, également repéré par la forme hexagonale du bloc.

Certains langages de programmation proposent un opérateur permettant de tester l'identité en mémoire de deux objets. C'est le cas de la comparaison de pointeurs en C, de `is` en Python, de `==` en OCaml, etc.

Voici un exemple en Python illustrant la différence entre les opérateurs `==` et `is` :

>>> lst_a = [1, 2, 3]
>>> lst_b = [1, 2, 3]
>>> lst_a is lst_b
False
>>> lst_a == lst_b
True

Mais :

>>> lst_c = lst_b
>>> lst_b is lst_c
True

Dans ce dernier cas, `lst_b` et `lst_c` désignent en réalité le même objet dans la mémoire.

Ces opérateurs et la nuance entre égalité de valeur et identité en mémoire mettent en jeu des concepts difficiles qui dépassent le périmètre de ce texte, nous nous contentons donc de signaler leur existence.

## 5.2. Opérateurs entre booléens

Comme les valeurs booléennes représentent naturellement des valeurs de vérité, les opérations élémentaires pour combiner des booléens sont de nature logique. Les opérateurs logiques usuels sont la conjonction « et », la disjonction « ou » et la négation « non ». Si A et B sont deux valeurs de vérité :

* conjonction : « A et B » est vrai lorsque les deux opérandes sont vrais,

* disjonction : « A ou B » est un *ou inclusif* : il est vrai lorsqu'au moins un des deux opérandes est vrai,

* négation : « non A » est vrai lorsque A est faux et inversement.
>>> 'ane' in 'banane'
True

Attention : l'opérateur not ayant une priorité plus faible que celle de l'opérateur in, l'expression not A in B doit être lue comme not (A in B) et non comme (not A) in B qui n'est probablement pas le sens souhaité. Une autre syntaxe possible (et potentiellement moins ambiguë) utilise l'opérateur dédié not in, comme dans l'expression A not in B.

Ces opérateurs existent également en Scratch pour les chaînes de caractères (en vert) et pour les listes (en orange) :

<figure><img src="image_9.png" /></figure>

Notons que la présence d'un point d'interrogation dans ces opérateurs (comme la formulation en langue naturelle de la plupart des autres blocs de Scratch) est un choix des concepteurs du langage. On peut supposer qu'elle a ici pour but d'insister sur le caractère booléen de ces opérateurs, également repéré par la forme hexagonale du bloc.

Certains langages de programmation proposent un opérateur permettant de tester l'identité en mémoire de deux objets. C'est le cas de la comparaison de pointeurs en C, de `is` en Python, de `==` en OCaml, etc.

Voici un exemple en Python illustrant la différence entre les opérateurs `==` et `is` :

>>> lst_a = [1, 2, 3]
>>> lst_b = [1, 2, 3]
>>> lst_a is lst_b
False
>>> lst_a == lst_b
True

Mais :

>>> lst_c = lst_b
>>> lst_b is lst_c
True

Dans ce dernier cas, `lst_b` et `lst_c` désignent en réalité le même objet dans la mémoire.

Ces opérateurs et la nuance entre égalité de valeur et identité en mémoire mettent en jeu des concepts difficiles qui dépassent le périmètre de ce texte, nous nous contentons donc de signaler leur existence.

## 5.2. Opérateurs entre booléens

Comme les valeurs booléennes représentent naturellement des valeurs de vérité, les opérations élémentaires pour combiner des booléens sont de nature logique. Les opérateurs logiques usuels sont la conjonction « et », la disjonction « ou » et la négation « non ». Si A et B sont deux valeurs de vérité :

* conjonction : « A et B » est vrai lorsque les deux opérandes sont vrais,

* disjonction : « A ou B » est un *ou inclusif* : il est vrai lorsqu'au moins un des deux opérandes est vrai,

* négation : « non A » est vrai lorsque A est faux et inversement.
Ces opérateurs existent dans tous les langages de programmation, même ceux qui n'ont pas de type booléen explicite. Dans ce dernier cas, ils renvoient une valeur dont l'interprétation comme valeur de vérité correspond à ces définitions.

On retrouve fréquemment deux syntaxes :

* Python et Scratch utilisent les mots : A and B, A or B, not A (Scratch en français utilise naturellement et, ou et non) ;

* Le langage C et ses dérivés utilisent `A && B` et `A || B` pour conjonction et disjonction et `!A` pour la négation.

Voici un exemple d'évaluation d'expressions booléennes dans l'interpréteur Python :

```python
>>> a = True
>>> b = False
>>> a or b
True
>>> a and b
False
>>> not a
False
```

## Évaluation séquentielle

Dans la plupart des langages de programmation, comme Python ou C, les opérateurs booléens ont un comportement particulier. Lors de l'évaluation des opérateurs `and` et `or`, le deuxième opérande n'est évalué que si c'est nécessaire, c'est-à-dire lorsque la valeur du premier opérande ne permet pas de déterminer à elle seule la valeur de l'expression<fnref n="10" />.

En Python<fnref n="11" />, dans l'expression `a and b` l'expression `b` n'est évaluée que si `a` est interprétée comme vraie. En effet, si `a` vaut `False`, `a` and `b` vaudra toujours `False` quelle que soit la valeur de `b`. Le fait que l'expression `à droite de and` ne soit pas évaluée du tout peut être utilisé dans une situation où son évaluation serait susceptible d'échouer.

De même, dans l'expression `a or b`, l'expression `b` n'est évaluée que si `a` est interprétée comme fausse. En effet, si `a` est vraie, `a` or `b` le sera aussi quelle que soit la valeur de `b`.

Ce mécanisme présente des avantages en termes de concision et d'aisance d'écriture. Par exemple, dans l'expression

```python
x >= 0 and sqrt(x) < 5
```

la fonction `sqrt` ne sera pas appelée si `x` est négatif (ce qui provoquerait une erreur). De même, si l'on suppose que `ma_liste` désigne une valeur de type `list`, l'évaluation de

```python
len(ma_liste) > 0 and ma_liste[0] == 'a'
```

ne provoque pas d'erreur, même si `ma_liste` est vide, car dans ce cas le membre gauche est faux et `ma_liste[0] == 'a'` n'est pas évaluée. Un autre avantage de ce type d'écriture est d'éviter l'imbrication de structures conditionnelles :

<footnote n="10">¹⁰Il n'est pas clair que ce soit le cas dans le langage Scratch.</footnote>

<footnote n="11">¹¹https://docs.python.org/3/reference/expressions.html#boolean-operations</footnote>
Ces opérateurs existent dans tous les langages de programmation, même ceux qui n'ont pas de type booléen explicite. Dans ce dernier cas, ils renvoient une valeur dont l'interprétation comme valeur de vérité correspond à ces définitions.

On retrouve fréquemment deux syntaxes :

* Python et Scratch utilisent les mots : A and B, A or B, not A (Scratch en français utilise naturellement et, ou et non) ;

* Le langage C et ses dérivés utilisent A && B et A || B pour conjonction et disjonction et !A pour la négation.

Voici un exemple d'évaluation d'expressions booléennes dans l'interpréteur Python :

```python
>>> a = True
>>> b = False
>>> a or b
True
>>> a and b
False
>>> not a
False
```

## Évaluation séquentielle

Dans la plupart des langages de programmation, comme Python ou C, les opérateurs booléens ont un comportement particulier. Lors de l'évaluation des opérateurs and et or, le deuxième opérande n'est évalué que si c'est nécessaire, c'est-à-dire lorsque la valeur du premier opérande ne permet pas de déterminer à elle seule la valeur de l'expression<fnref n="10" />.

En Python<fnref n="11" />, dans l'expression *a and b* l'expression *b* n'est évaluée que si *a* est interprétée comme vraie. En effet, si *a* vaut *False*, *a and b* vaudra toujours *False* quelle que soit la valeur de *b*. Le fait que l'expression à droite de *and* ne soit pas évaluée du tout peut être utilisé dans une situation où son évaluation serait susceptible d'échouer.

De même, dans l'expression *a or b*, l'expression *b* n'est évaluée que si *a* est interprétée comme fausse. En effet, si *a* est vraie, *a or b* le sera aussi quelle que soit la valeur de *b*.

Ce mécanisme présente des avantages en termes de concision et d'aisance d'écriture. Par exemple, dans l'expression

```python
x >= 0 and sqrt(x) < 5
```

la fonction sqrt ne sera pas appelée si *x* est négatif (ce qui provoquerait une erreur). De même, si l'on suppose que *ma_liste* désigne une valeur de type *list*, l'évaluation de

```python
len(ma_liste) > 0 and ma_liste[0] == 'a'
```

ne provoque pas d'erreur, même si *ma_liste* est vide, car dans ce cas le membre gauche est faux et *ma_liste[0] == 'a'* n'est pas évaluée. Un autre avantage de ce type d'écriture est d'éviter l'imbrication de structures conditionnelles :

<footnote n="10">¹⁰Il n'est pas clair que ce soit le cas dans le langage Scratch.</footnote>

<footnote n="11">¹¹https://docs.python.org/3/reference/expressions.html#boolean-operations</footnote>
```python
if len(ma_liste) > 0 and ma_liste[0] == 'a':
    print('la liste commence par un a')
else:
    print('la liste ne commence pas par un a')
```

est équivalent au code suivant, qui est plus explicite sur l'ordre d'évaluation mais nécessite de
répéter le deuxième cas :

```python
if len(ma_liste) > 0:
    if ma_liste[0] == 'a':
        print('la liste commence par un a')
    else:
        print('la liste ne commence pas par un a')
else:
    print('la liste ne commence pas par un a')
```

Conceptuellement, on peut considérer que la sémantique de l'affectation c = a and b est
proche de celle de la suite d'instructions

```python
if a:
    c = b
else:
    c = a
```

De même, lorsqu'on exécute l'affectation c = a or b, tout se passe comme si l'on exécutait la
suite d'instructions

```python
if a:
    c = a
else:
    c = b
```

En Python, tout objet peut être interprété comme un booléen donc l'écriture if a a un sens quel
que soit le type de a. Ainsi, les expressions a and b et a or b sont, selon les cas, du type de a ou
du type de b, même si ce n'est pas un booléen.

Ce mécanisme peut provoquer des phénomènes inattendus. Supposons qu'un exercice demande
d'écrire une fonction renvoyant un couple contenant le minimum et le maximum d'une liste. Voici
une production (authentique) d'élève en réponse à cette question :

```python
def min_et_max(lst):
    return (min(lst) and max(lst))
```

Ce code est syntaxiquement correct, mais l'opérateur booléen and a été utilisé à la place d'une
virgule. On peut supposer que l'auteur a utilisé cet opérateur dans son sens courant afin de traduire
le « et » de l'énoncé<fnref n="12" />. Tester cette fonction fait apparaître des résultats potentiellement difficiles à
interpréter :

<footnote n="12">¹²Il est à noter que l'erreur peut persister même sans occurrence du mot « et » dans l'énoncé : quand on demande aux élèves de renvoyer un couple (U, V) de valeurs numériques, certains écrivent return U and V.</footnote>
```python
if len(ma_liste) > 0 and ma_liste[0] == 'a':
    print('la liste commence par un a')
else:
    print('la liste ne commence pas par un a')
```

est équivalent au code suivant, qui est plus explicite sur l'ordre d'évaluation mais nécessite de
répéter le deuxième cas :

```python
if len(ma_liste) > 0:
    if ma_liste[0] == 'a':
        print('la liste commence par un a')
    else:
        print('la liste ne commence pas par un a')
else:
    print('la liste ne commence pas par un a')
```

Conceptuellement, on peut considérer que la sémantique de l'affectation c = a and b est
proche de celle de la suite d'instructions

```python
if a:
    c = b
else:
    c = a
```

De même, lorsqu'on exécute l'affectation c = a or b, tout se passe comme si l'on exécutait la
suite d'instructions

```python
if a:
    c = a
else:
    c = b
```

En Python, tout objet peut être interprété comme un booléen donc l'écriture if a a un sens quel
que soit le type de a. Ainsi, les expressions a and b et a or b sont, selon les cas, du type de a ou
du type de b, même si ce n'est pas un booléen.

Ce mécanisme peut provoquer des phénomènes inattendus. Supposons qu'un exercice demande
d'écrire une fonction renvoyant un couple contenant le minimum et le maximum d'une liste. Voici
une production (authentique) d'élève en réponse à cette question :

```python
def min_et_max(lst):
    return (min(lst) and max(lst))
```

Ce code est syntaxiquement correct, mais l'opérateur booléen and a été utilisé à la place d'une
virgule. On peut supposer que l'auteur a utilisé cet opérateur dans son sens courant afin de traduire
le « et » de l'énoncé<fnref n="12" />. Tester cette fonction fait apparaître des résultats potentiellement difficiles à
interpréter :

<footnote n="12">¹²Il est à noter que l'erreur peut persister même sans occurrence du mot « et » dans l'énoncé : quand on demande aux élèves de renvoyer un couple (U, V) de valeurs numériques, certains écrivent return U and V.</footnote>
```python
>>> min_et_max([2, -5, 3])
3
```

Cet appel renvoie l'élément maximum 3 puisque le minimum -5 est interprété comme vrai et que l'expression -5 and 3 s'évalue donc en 3.

```python
>>> min_et_max([2, 0, 3])
0
```

Cet appel renvoie l'élément minimum 0 puisqu'il est interprété comme faux. Les résultats sont évidemment tous incorrects puisqu'ils sont de type `int` et non `tuple`, mais ne provoquent pas d'erreur à l'exécution. Ils ne mettent pas non plus en évidence le fait que `and` est un opérateur booléen.

## Confusion possible avec les opérateurs bit à bit

Dans les langages de programmation de la famille du C il existe des opérateurs `&` et `|` mais ce ne sont pas des opérateurs booléens : ils agissent (bit par bit) sur l'écriture binaire des entiers. Ces opérateurs ont un sens très différent des opérateurs `&&` et `||`, et il est facile de confondre leurs syntaxes.

En Python il existe de même `&`, `|`, `^` et un certain nombre d'autres opérateurs qui acceptent exclusivement des opérandes de type numérique (y compris de type `bool`) et calculent des opérations sur l'écriture binaire des nombres<fnref n="13" />.

Même si leur comportement est proche de celui des opérateurs booléens quand on les applique à des opérandes de valeur `True` ou `False`, il est considéré comme très risqué voire incorrect de les utiliser de cette manière.

Voici un exemple en Python qui illustre la différence entre les opérateurs `and` et `&` :

```python
>>> 4 and 2  # ici 4 est interprété comme 'vrai'
2
>>> 4 & 2   # 4 s'écrit 100 et 2 s'écrit 010 en binaire
0
>>> 0b100 & 0b010  # équivalent à l'expression précédente
0
```

L'exemple suivant montre que l'opérateur `&` n'est pas défini sur les types non numériques, contrairement à l'opérateur `and` :

```python
>>> "a" and 12
12
>>> "a" & 12
...
TypeError: unsupported operand type(s) for &: 'str' and 'int'
```

<footnote n="13">¹³https://docs.python.org/fr/3/library/stdtypes.html#bitwise-operations-on-integer-types</footnote>
```python
>>> min_et_max([2, -5, 3])
3
```

Cet appel renvoie l'élément maximum 3 puisque le minimum -5 est interprété comme vrai et que l'expression -5 and 3 s'évalue donc en 3.

```python
>>> min_et_max([2, 0, 3])
0
```

Cet appel renvoie l'élément minimum 0 puisqu'il est interprété comme faux. Les résultats sont évidemment tous incorrects puisqu'ils sont de type `int` et non `tuple`, mais ne provoquent pas d'erreur à l'exécution. Ils ne mettent pas non plus en évidence le fait que `and` est un opérateur booléen.

## Confusion possible avec les opérateurs bit à bit

Dans les langages de programmation de la famille du C il existe des opérateurs `&` et `|` mais ce ne sont pas des opérateurs booléens : ils agissent (bit par bit) sur l'écriture binaire des entiers. Ces opérateurs ont un sens très différent des opérateurs `&&` et `||`, et il est facile de confondre leurs syntaxes.

En Python il existe de même `&`, `|`, `^` et un certain nombre d'autres opérateurs qui acceptent exclusivement des opérandes de type numérique (y compris de type `bool`) et calculent des opérations sur l'écriture binaire des nombres<fnref n="13" />.

Même si leur comportement est proche de celui des opérateurs booléens quand on les applique à des opérandes de valeur `True` ou `False`, il est considéré comme très risqué voire incorrect de les utiliser de cette manière.

Voici un exemple en Python qui illustre la différence entre les opérateurs `and` et `&` :

```python
>>> 4 and 2  # ici 4 est interprété comme 'vrai'
2
>>> 4 & 2   # 4 s'écrit 100 et 2 s'écrit 010 en binaire
0
>>> 0b100 & 0b010  # équivalent à l'expression précédente
0
```

L'exemple suivant montre que l'opérateur `&` n'est pas défini sur les types non numériques, contrairement à l'opérateur `and` :

```python
>>> "a" and 12
12
>>> "a" & 12
...
TypeError: unsupported operand type(s) for &: 'str' and 'int'
```

<footnote n="13">¹³https://docs.python.org/fr/3/library/stdtypes.html#bitwise-operations-on-integer-types</footnote>
## Expressions conditionnelles

En Python, comme dans d'autres langages comme le C, il existe un opérateur ternaire permettant de construire des *expressions conditionnelles*, dont voici un exemple :

```python
-1 if n < 0 else +1
```

Il s'agit bien d'une *expression*, qui s'évalue en une *valeur*, et qu'on peut par exemple utiliser comme partie droite d'une affectation :

```python
signe = -1 if n < 0 else +1
```

Voici un autre exemple utilisant des chaînes :

```python
print("il y a", n,
      ("journal" if n <= 1 else "journaux"))
```

Des constructions équivalentes sont possibles dans la plupart des autres langages de programmation.

Il n'est pas indispensable de présenter cette structure à des élèves débutants. Elle est en particulier susceptible d'entraîner une confusion avec la structure de contrôle `if ... else`.

## 5.3. Fonctions à valeur booléenne

Pour tester des propriétés plus complexes que de simples comparaisons, on a naturellement recours à des fonctions qui renvoient des valeurs booléennes. On appelle parfois ce type de fonctions des *prédicats* par analogie avec la logique mathématique.

Conceptuellement, les fonctions à valeur booléenne sont similaires aux opérateurs à valeur booléenne comme les comparaisons. Les opérateurs servent simplement à donner une syntaxe plus concise pour des opérations fréquentes.<fnref n="14" />

Dans cette section, on donne quelques exemples de fonctions booléennes prédéfinies du langage Python, puis de définitions de telles fonctions.

### Fonctions booléennes prédéfinies en Python

Le type prédéfini `str` fournit un grand nombre de méthodes à valeur booléenne : `isalpha()` – est composé uniquement de lettres –, `isdigit()` – uniquement de chiffres –, `islower()` ou `isupper()` – est entièrement en minuscules ou entièrement en majuscules, `istitle()` – commence par une majuscule puis ne contient que des minuscules – etc. La liste de ces méthodes et leur description détaillée est disponible dans la documentation en ligne.

La fonction `all()` reçoit comme paramètre un objet itérable (par exemple une liste), et renvoie `True` si tous les éléments de la liste sont interprétables comme la valeur de vérité « vrai », et `False` sinon. Cette fonction imite le comportement du quantificateur universel $\forall$ en mathématiques.

```python
>>> prenoms = ['Sylvie', 'Cécile', 'Anne', 'Malika']
>>> avec_majuscule = [prenom.istitle() for prenom in prenoms]
>>> all(avec_majuscule)  # tous ont une majuscule en début de prénom
True
```

Cette fonction est souvent combinée avec la fonction `map` (appliquer pour tous) :

<footnote n="14">¹⁴Les cas des opérateurs `and` et `or` et de l'opérateur ternaire présenté plus haut sont particuliers parce qu'ils n'évaluent pas forcément toutes leurs opérandes, ce qui ne peut pas se définir avec une fonction.</footnote>
## Expressions conditionnelles

En Python, comme dans d'autres langages comme le C, il existe un opérateur ternaire permettant de construire des *expressions conditionnelles*, dont voici un exemple :

```
-1 if n < 0 else +1
```

Il s'agit bien d'une *expression*, qui s'évalue en une *valeur*, et qu'on peut par exemple utiliser comme partie droite d'une affectation :

```
signe = -1 if n < 0 else +1
```

Voici un autre exemple utilisant des chaînes :

```
print("il y a", n,
      ("journal" if n <= 1 else "journaux"))
```

Des constructions équivalentes sont possibles dans la plupart des autres langages de programmation.

Il n'est pas indispensable de présenter cette structure à des élèves débutants. Elle est en particulier susceptible d'entraîner une confusion avec la structure de contrôle `if ... else`.

## 5.3. Fonctions à valeur booléenne

Pour tester des propriétés plus complexes que de simples comparaisons, on a naturellement recours à des fonctions qui renvoient des valeurs booléennes. On appelle parfois ce type de fonctions des *prédicats* par analogie avec la logique mathématique.

Conceptuellement, les fonctions à valeur booléenne sont similaires aux opérateurs à valeur booléenne comme les comparaisons. Les opérateurs servent simplement à donner une syntaxe plus concise pour des opérations fréquentes.<fnref n="14" />

Dans cette section, on donne quelques exemples de fonctions booléennes prédéfinies du langage Python, puis de définitions de telles fonctions.

### Fonctions booléennes prédéfinies en Python

Le type prédéfini `str` fournit un grand nombre de méthodes à valeur booléenne : `isalpha()` – est composé uniquement de lettres –, `isdigit()` – uniquement de chiffres –, `islower()` ou `isupper()` – est entièrement en minuscules ou entièrement en majuscules, `istitle()` – commence par une majuscule puis ne contient que des minuscules – etc. La liste de ces méthodes et leur description détaillée est disponible dans la documentation en ligne.

La fonction `all()` reçoit comme paramètre un objet itérable (par exemple une liste), et renvoie `True` si tous les éléments de la liste sont interprétables comme la valeur de vérité « vrai », et `False` sinon. Cette fonction imite le comportement du quantificateur universel $\forall$ en mathématiques.

```python
>>> prenoms = ['Sylvie', 'Cécile', 'Anne', 'Malika']
>>> avec_majuscule = [prenom.istitle() for prenom in prenoms]
>>> all(avec_majuscule)  # tous ont une majuscule en début de prénom
True
```

Cette fonction est souvent combinée avec la fonction `map` (appliquer pour tous) :

<footnote n="14">¹⁴Les cas des opérateurs `and` et `or` et de l'opérateur ternaire présenté plus haut sont particuliers parce qu'ils n'évaluent pas forcément toutes leurs opérandes, ce qui ne peut pas se définir avec une fonction.</footnote>
```python
>>> all(map(str.istitle, prenoms))
True
>>> all(s.istitle() for s in prenoms)
True
```

La fonction prédéfinie any() est similaire. Elle correspond au quantificateur existentiel ∃, elle renvoie True si un itérable contient au moins une valeur interprétable comme True, et False sinon.

```python
>>> nombres = [0, 1, 2, 3, 4, 5, 6]
>>> impair = [n % 2 == 1 for n in nombres]  # indique les éléments impairs
>>> any impair  # il y a au moins un nombre impair
True
```

Remarquons que dans cet exemple, lors du calcul de la liste impair, le test `n % 2 == 1` est évalué pour tous les éléments de la liste nombres. Si on cherche à savoir si cette liste contient au moins un nombre impair, il n'est en fait pas nécessaire de faire le test sur tous, il suffit de s'arrêter au premier cas trouvé. Python permet de réaliser ce comportement au moyen des expressions génératrices qui sont en quelque sorte des listes « paresseuses » dont les éléments ne sont évalués que si nécessaire. On écrirait alors:

```python
>>> nombres = [0, 1, 2, 3, 4, 5, 6]
>>> any(n % 2 == 1 for n in nombres)
True
```

On ne détaillera pas plus cette fonctionnalité qui dépasse le cadre du présent article.

Enfin, mentionnons la fonction bool, qui construit une valeur booléenne à l'aide de son argument, comme toute fonction dont le nom est celui d'un type prédéfini. Cela revient à appliquer les règles qui déterminent la valeur de vérité d'une valeur quelconque en fonction de son type :

```python
>>> bool(0)
False
>>> bool('zéro')
True
```

D'autres exemples sont donnés en annexe 9.2.

## Définition de fonctions à valeur booléenne

La définition d'une telle fonction se base sur d'autres fonctions ou opérateurs booléens pour calculer son résultat. Voici un exemple simple :

```python
def est_impair(n):
    return (n % 2) == 1
```

Une fois définie, cette fonction peut être utilisée en tant que condition :

```python
if est_impair(n):
    ...
else:
    ...
```
```python
>>> all(map(str.istitle, prenoms))
True
>>> all(s.istitle() for s in prenoms)
True
```

La fonction prédéfinie any() est similaire. Elle correspond au quantificateur existentiel ∃, elle renvoie True si un itérable contient au moins une valeur interprétable comme True, et False sinon.

```python
>>> nombres = [0, 1, 2, 3, 4, 5, 6]
>>> impair = [n % 2 == 1 for n in nombres]  # indique les éléments impairs
>>> any impair  # il y a au moins un nombre impair
True
```

Remarquons que dans cet exemple, lors du calcul de la liste impair, le test `n % 2 == 1` est évalué pour tous les éléments de la liste nombres. Si on cherche à savoir si cette liste contient au moins un nombre impair, il n'est en fait pas nécessaire de faire le test sur tous, il suffit de s'arrêter au premier cas trouvé. Python permet de réaliser ce comportement au moyen des expressions génératrices qui sont en quelque sorte des listes « paresseuses » dont les éléments ne sont évalués que si nécessaire. On écrirait alors:

```python
>>> nombres = [0, 1, 2, 3, 4, 5, 6]
>>> any(n % 2 == 1 for n in nombres)
True
```

On ne détaillera pas plus cette fonctionnalité qui dépasse le cadre du présent article.

Enfin, mentionnons la fonction bool, qui construit une valeur booléenne à l'aide de son argument, comme toute fonction dont le nom est celui d'un type prédéfini. Cela revient à appliquer les règles qui déterminent la valeur de vérité d'une valeur quelconque en fonction de son type :

```python
>>> bool(0)
False
>>> bool('zéro')
True
```

D'autres exemples sont donnés en annexe 9.2.

## Définition de fonctions à valeur booléenne

La définition d'une telle fonction se base sur d'autres fonctions ou opérateurs booléens pour calculer son résultat. Voici un exemple simple :

```python
def est_impair(n):
    return (n % 2) == 1
```

Une fois définie, cette fonction peut être utilisée en tant que condition :

```python
if est_impair(n):
    ...
else:
    ...
```
En termes logiques, une telle fonction peut être appelée *prédicat* puisqu'elle détermine une valeur de vérité en fonction de l'objet qu'elle reçoit en argument. Les raisons qui peuvent justifier la définition de fonctions à valeurs booléennes sont les mêmes que pour n'importe quelle définition de fonction : cela permet de nommer une certaine expression pour gagner en lisibilité, d'éviter la répétition si le prédicat sert plusieurs fois, ou encore de mieux structurer le programme si le calcul du prédicat est complexe.

## 5.4. Écritures redondantes

Comme on l'a vu, les valeurs calculées par des expressions booléennes peuvent aussi bien servir comme conditions dans les structures de contrôle qu'être stockées dans des variables, comme on le fait pour les valeurs numériques ou textuelles. Il y a donc un rôle de contrôle et un rôle de donnée, qui correspondent à des intentions différentes. Le fait qu'une même valeur puisse servir aux deux rôles dans un même programme est conceptuellement difficile pour les débutants en programmation et explique certaines tournures redondantes que nous détaillons dans la suite.

### Comparaisons inutiles dans des expressions

Supposons qu'on souhaite déterminer si un nombre *n* est divisible par 3 ou 5. Une fois définies les deux variables

est_multiple_de_3 = (nombre % 3 == 0)

est_multiple_de_5 = (nombre % 5 == 0)

on peut initialiser la variable booléenne

est_multiple_de_3_ou_5 = est_multiple_de_3 or est_multiple_de_5

dont la valeur est True si et seulement si *n* est divisible par 3 ou par 5 (ou les deux). On rencontre parfois l'écriture équivalente :

est_multiple_de_3_ou_5 = (est_multiple_de_3 == True) or (est_multiple_de_5 == True)

Quoique correcte, cette expression effectue des comparaisons inutiles. En effet, une variable *b* qui contient un booléen a la même valeur de vérité que l'expression *b* == True. On préférera aussi en général utiliser l'expression booléenne *not b* plutôt que *b* == False.

Le guide de style de Python PEP 8 (van Rossum et al., 2013) signale qu'il est déconseillé de comparer les résultats d'expressions booléennes aux littéraux True ou False. D'expérience, il est cependant fréquent que des programmeurs peu expérimentés recourent à ce genre d'écriture.

### Comparaisons inutiles dans des structures de contrôle

On rencontre fréquemment des structures conditionnelles de la forme suivante :

if est_bissextile == True:
    nombre_jours = nombre_jours + 1

Si *est_bissextile* est une variable booléenne, cela est équivalent à la formulation plus simple suivante :

if est_bissextile:
    nombre_jours = nombre_jours + 1
En termes logiques, une telle fonction peut être appelée *prédicat* puisqu'elle détermine une valeur de vérité en fonction de l'objet qu'elle reçoit en argument. Les raisons qui peuvent justifier la définition de fonctions à valeurs booléennes sont les mêmes que pour n'importe quelle définition de fonction : cela permet de nommer une certaine expression pour gagner en lisibilité, d'éviter la répétition si le prédicat sert plusieurs fois, ou encore de mieux structurer le programme si le calcul du prédicat est complexe.

## 5.4. Écritures redondantes

Comme on l'a vu, les valeurs calculées par des expressions booléennes peuvent aussi bien servir comme conditions dans les structures de contrôle qu'être stockées dans des variables, comme on le fait pour les valeurs numériques ou textuelles. Il y a donc un rôle de contrôle et un rôle de donnée, qui correspondent à des intentions différentes. Le fait qu'une même valeur puisse servir aux deux rôles dans un même programme est conceptuellement difficile pour les débutants en programmation et explique certaines tournures redondantes que nous détaillons dans la suite.

### Comparaisons inutiles dans des expressions

Supposons qu'on souhaite déterminer si un nombre *n* est divisible par 3 ou 5. Une fois définies les deux variables

est_multiple_de_3 = (nombre % 3 == 0)

est_multiple_de_5 = (nombre % 5 == 0)

on peut initialiser la variable booléenne

est_multiple_de_3_ou_5 = est_multiple_de_3 or est_multiple_de_5

dont la valeur est True si et seulement si *n* est divisible par 3 ou par 5 (ou les deux). On rencontre parfois l'écriture équivalente :

est_multiple_de_3_ou_5 =
( est_multiple_de_3 == True ) or ( est_multiple_de_5 == True )

Quoique correcte, cette expression effectue des comparaisons inutiles. En effet, une variable *b* qui contient un booléen a la même valeur de vérité que l'expression *b* == True. On préférera aussi en général utiliser l'expression booléenne *not b* plutôt que *b* == False.

Le guide de style de Python PEP 8 (van Rossum et al., 2013) signale qu'il est déconseillé de comparer les résultats d'expressions booléennes aux littéraux True ou False. D'expérience, il est cependant fréquent que des programmeurs peu expérimentés recourent à ce genre d'écriture.

### Comparaisons inutiles dans des structures de contrôle

On rencontre fréquemment des structures conditionnelles de la forme suivante :

if est_bissextile == True:
    nombre_jours = nombre_jours + 1

Si *est_bissextile* est une variable booléenne, cela est équivalent à la formulation plus simple suivante :

if est_bissextile:
    nombre_jours = nombre_jours + 1
Le fait de comparer une variable booléenne à True dans une structure conditionnelle est le signe que quelque chose n'a pas été compris dans la notion de valeur booléenne : l'élève pense par exemple qu'une condition doit forcément contenir un opérateur de comparaison. Or l'expression est_bissextile, réduite à une variable, n'en contient pas, de ce fait il est possible qu'elle ne soit pas perçue comme une condition valide. Ainsi, une comparaison entre nombres comme n >= 0 sera plus spontanément perçue comme une condition valable, c'est pourquoi les élèves n'écriront pas

```python
if (n >= 0) == True:
    resultat = "positif"
```

mais bien

```python
if n >= 0:
    resultat = "positif"
```

Ce phénomène se produit également lors de l'appel à des fonctions qui renvoient une valeur booléenne. Ainsi, au lieu d'une condition comme

```python
if est_premier(n):
    ... # traitement
```

on rencontrera parfois le code redondant suivant :

```python
if est_premier(n) == True:
    ... # traitement
```

On remarque la même chose lors de l'usage de boucle while. Si continuer est un booléen, on préférera

```python
while continuer:
    ... # traitement
```

à

```python
while continuer == True:
    ... # traitement
```

## Structures conditionnelles superflues

Dans les fonctions renvoyant une valeur booléenne, on trouve régulièrement dans les productions d'élèves un code de la forme :

```python
def est_pair(n):
    if n % 2 == 0:
        return True
    else:
        return False
```

Cette fonction peut être remplacée par :
Le fait de comparer une variable booléenne à True dans une structure conditionnelle est le signe que quelque chose n'a pas été compris dans la notion de valeur booléenne : l'élève pense par exemple qu'une condition doit forcément contenir un opérateur de comparaison. Or l'expression est_bissextile, réduite à une variable, n'en contient pas, de ce fait il est possible qu'elle ne soit pas perçue comme une condition valide. Ainsi, une comparaison entre nombres comme n >= 0 sera plus spontanément perçue comme une condition valable, c'est pourquoi les élèves n'écriront pas

```python
if (n >= 0) == True:
    resultat = "positif"
```

mais bien

```python
if n >= 0:
    resultat = "positif"
```

Ce phénomène se produit également lors de l'appel à des fonctions qui renvoient une valeur booléenne. Ainsi, au lieu d'une condition comme

```python
if est_premier(n):
    ... # traitement
```

on rencontrera parfois le code redondant suivant :

```python
if est_premier(n) == True:
    ... # traitement
```

On remarque la même chose lors de l'usage de boucle while. Si continuer est un booléen, on préférera

```python
while continuer:
    ... # traitement
```

à

```python
while continuer == True:
    ... # traitement
```

## Structures conditionnelles superflues

Dans les fonctions renvoyant une valeur booléenne, on trouve régulièrement dans les productions d'élèves un code de la forme :

```python
def est_pair(n):
    if n % 2 == 0:
        return True
    else:
        return False
```

Cette fonction peut être remplacée par :
```python
def est_pair(n):
    return n % 2 == 0
```

En effet, si la condition `n % 2 == 0` vaut True, la fonction renvoie True et si la condition vaut False, la fonction renvoie False. Autrement dit dans tous les cas la fonction renvoie la valeur de vérité de la condition. Renvoyer simplement la valeur de `n % 2 == 0` permet donc d'éviter un branchement conditionnel superflu.

Il y a des considérations analogues pour les deux séquences suivantes:

```python
def est_impair(n):
    if est_pair(n):
        return False
    else:
        return True
```

Dans tous les cas la valeur renvoyée est celle de `not est_pair(n)`. On préfèrera donc :

```python
def est_impair(n):
    return not est_pair(n)
```

Bien entendu, le même principe s'applique quelle que soit la complexité de l'expression booléenne utilisée dans la condition.

```python
def est_multiple_de_trois_ou_cinq(nombre):
    if nombre % 3 == 0 or nombre % 5 == 0:
        return True
    else:
        return False
```

Il est donc possible de réécrire cette fonction de manière plus concise :

```python
def multiple_de_trois_ou_cinq(nombre):
    return nombre % 3 == 0 or nombre % 5 == 0
```

Si certains élèves ont du mal à s'en convaincre, il est toujours possible d'utiliser une table de vérité pour les y aider :

<table><thead><tr><th>n % 3 == 0</th><th>n % 5 == 0</th><th>n % 3 == 0 or n % 5 == 0</th><th>Valeur de retour</th></tr></thead><tbody><tr><td>True</td><td>True</td><td>True</td><td>True</td></tr><tr><td>True</td><td>False</td><td>True</td><td>True</td></tr><tr><td>False</td><td>True</td><td>True</td><td>True</td></tr><tr><td>False</td><td>False</td><td>False</td><td>False</td></tr></tbody></table>

Ceci met en évidence le fait qu'une valeur booléenne est une valeur comme une autre (au même titre qu'un entier ou une chaîne de caractères par exemple). Cette version peut être considérée comme plus lisible, à condition d'avoir compris le statut des expressions et valeurs booléennes.

Par ailleurs, la seconde version peut être marginalement plus rapide que la première à l'exécution. Cependant la différence est minime et même difficilement observable, ce qui fait que l'efficacité pure n'est pas un argument fort en faveur d'une forme ou de l'autre. D'autre part, on se gardera bien de confondre la concision et l'efficacité.

On pourra alors réserver la forme longue avec instruction conditionnelle à des blocs nécessitant plusieurs instructions ou des expressions plus complexes, comme dans ce deuxième exemple :
```python
def est_pair(n):
    return n % 2 == 0
```

En effet, si la condition `n % 2 == 0` vaut `True`, la fonction renvoie `True` et si la condition vaut `False`, la fonction renvoie `False`. Autrement dit dans tous les cas la fonction renvoie la valeur de vérité de la condition. Renvoyer simplement la valeur de `n % 2 == 0` permet donc d'éviter un branchement conditionnel superflu.

Il y a des considérations analogues pour les deux séquences suivantes:

```python
def est_impair(n):
    if est_pair(n):
        return False
    else:
        return True
```

Dans tous les cas la valeur renvoyée est celle de `not est_pair(n)`. On préfèrera donc :

```python
def est_impair(n):
    return not est_pair(n)
```

Bien entendu, le même principe s'applique quelle que soit la complexité de l'expression booléenne utilisée dans la condition.

```python
def est_multiple_de_trois_ou_cinq(nombre):
    if nombre % 3 == 0 or nombre % 5 == 0:
        return True
    else:
        return False
```

Il est donc possible de réécrire cette fonction de manière plus concise :

```python
def multiple_de_trois_ou_cinq(nombre):
    return nombre % 3 == 0 or nombre % 5 == 0
```

Si certains élèves ont du mal à s'en convaincre, il est toujours possible d'utiliser une table de vérité pour les y aider :

<table><thead><tr><th>n % 3 == 0</th><th>n % 5 == 0</th><th>n % 3 == 0 or n % 5 == 0</th><th>Valeur de retour</th></tr></thead><tbody><tr><td>True</td><td>True</td><td>True</td><td>True</td></tr><tr><td>True</td><td>False</td><td>True</td><td>True</td></tr><tr><td>False</td><td>True</td><td>True</td><td>True</td></tr><tr><td>False</td><td>False</td><td>False</td><td>False</td></tr></tbody></table>

Ceci met en évidence le fait qu'une valeur booléenne est une valeur comme une autre (au même titre qu'un entier ou une chaîne de caractères par exemple). Cette version peut être considérée comme plus lisible, à condition d'avoir compris le statut des expressions et valeurs booléennes.

Par ailleurs, la seconde version peut être marginalement plus rapide que la première à l'exécution. Cependant la différence est minime et même difficilement observable, ce qui fait que l'efficacité pure n'est pas un argument fort en faveur d'une forme ou de l'autre. D'autre part, on se gardera bien de confondre la concision et l'efficacité.

On pourra alors réserver la forme longue avec instruction conditionnelle à des blocs nécessitant plusieurs instructions ou des expressions plus complexes, comme dans ce deuxième exemple :
```python
def cas_de_base(L):
    if len(L) == 0:
        return True
    else:
        return L[0] == 0
```

On peut néanmoins également écrire :

```python
def cas_de_base(L):
    return len(L) == 0 or L[0] == 0
```

Attention, dans ce dernier cas la fonction s’appuie sur le mécanisme d’évaluation séquentiel particulier des opérateurs booléens que nous avons décrit en section 5.2. En effet, le test sur la nullité du premier élément de la liste ne sera effectué que si la liste n’est pas vide. Si la liste est vide, le deuxième opérande n’est pas évalué. Écrire l’expression dans l’autre sens ne fonctionnerait pas si la liste L était vide.

Comme le montrent ces exemples, il est nécessaire de rechercher un bon compromis entre difficulté conceptuelle pour les élèves, longueur et efficacité du code, respect des bonnes pratiques communément admises, et d’annoncer clairement les éventuelles exigences aux élèves.

Généralement il est préférable de remplacer

```python
def fonction():
    if expression_a_valeur_booleanenne:  # l'expression est une condition
        return True
    else:
        return False
```

par

```python
def fonction():
    return expression_a_valeur_booleanenne
```

et

```python
def fonction():
    if expression_a_valeur_booleanenne:
        return False
    else:
        return True
```

par

```python
def fonction():
    return not expression_a_valeur_booleanenne
```
```python
def cas_de_base(L):
    if len(L) == 0:
        return True
    else:
        return L[0] == 0
```

On peut néanmoins également écrire :

```python
def cas_de_base(L):
    return len(L) == 0 or L[0] == 0
```

Attention, dans ce dernier cas la fonction s’appuie sur le mécanisme d’évaluation séquentiel particulier des opérateurs booléens que nous avons décrit en section 5.2. En effet, le test sur la nullité du premier élément de la liste ne sera effectué que si la liste n’est pas vide. Si la liste est vide, le deuxième opérande n’est pas évalué. Écrire l’expression dans l’autre sens ne fonctionnerait pas si la liste L était vide.

Comme le montrent ces exemples, il est nécessaire de rechercher un bon compromis entre difficulté conceptuelle pour les élèves, longueur et efficacité du code, respect des bonnes pratiques communément admises, et d’annoncer clairement les éventuelles exigences aux élèves.

Généralement il est préférable de remplacer

```python
def fonction():
    if expression_a_valeur_booleanenne:  # l'expression est une condition
        return True
    else:
        return False
```

par

```python
def fonction():
    return expression_a_valeur_booleanenne
```

et

```python
def fonction():
    if expression_a_valeur_booleanenne:
        return False
    else:
        return True
```

par

```python
def fonction():
    return not expression_a_valeur_booleanenne
```
## *Considérations didactiques*

Il nous semble préférable de ne pas sanctionner les divers types de redondances évoquées ci-dessus dans les productions d'élèves débutants. Cependant, comprendre la notion d'expression à valeur booléenne est un objectif d'apprentissage important, c'est pourquoi il peut être judicieux d'insister sur l'existence de la forme concise quand l'occasion se présente.

Nous formulons l'hypothèse que ces différents cas de comparaisons redondantes sont liées au double rôle du booléen, à la fois donnée et condition influant sur le flot d'exécution et témoignent d'un niveau de conceptualisation insuffisant du concept de booléen. Ces questions sont par exemple discutées en détail par Lagrange & Rogalski (2017).

Ainsi, en présence d'une variable *b* désignant un booléen, le fait d'écrire `if b == True` laisse supposer que, dans l'esprit de l'apprenant, l'expression *b* a purement le statut de donnée et que le fait d'écrire explicitement la comparaison `b == True` produit une expression qui a le statut de condition. Réciproquement, l'emploi de *return True* et *return False* dans les deux branches d'un `if` laisse supposer que l'expression qui suit `if n` a que le statut de condition et pas celui de donnée qui rendrait légitime son emploi comme valeur de retour de fonction.

Une autre difficulté d'ordre didactique est que les formes redondantes sont syntaxiquement et sémantiquement valides, et d'une rapidité d'exécution indiscernable des formes courtes. Nous ne sommes pas en mesure à l'heure actuelle de proposer des situations susceptibles de permettre de travailler le dépassement de ces formulations naïves et où le passage à des formes concises est indispensable.<fnref n="15" />

Remarquons cependant qu'on constate parfois moins de gêne dans le cas d'expressions de comparaison « explicites » (par exemple `if x < 0: ...`). Le problème apparaît surtout dans le cas d'appels de fonctions à valeur booléenne (`if est_pair(n): ...`) ou l'usage de variables booléennes (`if propriete: ...`). Il devrait être possible de mettre à l'épreuve cette idée en remplaçant une expression booléenne par un appel de fonction équivalent (et vice-versa) comme dans l'exemple ci-dessous.

```python
def estpronatif(x):
    if x < 0:
        return True
    else:
        return False
```

```python
def estpronatif(x):
    return x < 0
```

Ceci devrait permettre de faire apparaître des expressions qui « questionneront » les conceptions déjà présentes des élèves (`if estpronatif(x) == True: ...` remplacé par `if (x < 0) == True: ...`), mais cela reste à vérifier.

# 6. Archétypes de variables booléennes

On s'intéresse dans cette section à quelques cas d'usage typiques de variables booléennes, en s'inspirant de la catégorisation des rôles de variables par Samurçay (1985), enrichie par Sajaniemi (2002) et reprise par Sorva et al. (2007).

<footnote n="15">¹⁵Plus généralement, les consignes ou recommandations liées au style peuvent être difficiles à motiver auprès des élèves par des rétroactions directes du milieu, même quand elles sont issues de consensus d'experts en réponse à des nécessités pratiques. C'est le cas notamment pour les usages décrits par la « PEP 8 » déjà mentionnée (van Rossum et al., 2013).</footnote>
## *Considérations didactiques*

Il nous semble préférable de ne pas sanctionner les divers types de redondances évoquées ci-dessus dans les productions d'élèves débutants. Cependant, comprendre la notion d'expression à valeur booléenne est un objectif d'apprentissage important, c'est pourquoi il peut être judicieux d'insister sur l'existence de la forme concise quand l'occasion se présente.

Nous formulons l'hypothèse que ces différents cas de comparaisons redondantes sont liées au double rôle du booléen, à la fois donnée et condition influant sur le flot d'exécution et témoignent d'un niveau de conceptualisation insuffisant du concept de booléen. Ces questions sont par exemple discutées en détail par Lagrange & Rogalski (2017).

Ainsi, en présence d'une variable *b* désignant un booléen, le fait d'écrire `if b == True` laisse supposer que, dans l'esprit de l'apprenant, l'expression *b* a purement le statut de donnée et que le fait d'écrire explicitement la comparaison `b == True` produit une expression qui a le statut de condition. Réciproquement, l'emploi de *return True* et *return False* dans les deux branches d'un `if` laisse supposer que l'expression qui suit `if n` a que le statut de condition et pas celui de donnée qui rendrait légitime son emploi comme valeur de retour de fonction.

Une autre difficulté d'ordre didactique est que les formes redondantes sont syntaxiquement et sémantiquement valides, et d'une rapidité d'exécution indiscernable des formes courtes. Nous ne sommes pas en mesure à l'heure actuelle de proposer des situations susceptibles de permettre de travailler le dépassement de ces formulations naïves et où le passage à des formes concises est indispensable.<fnref n="15" />

Remarquons cependant qu'on constate parfois moins de gêne dans le cas d'expressions de comparaison « explicites » (par exemple `if x < 0: ...`). Le problème apparaît surtout dans le cas d'appels de fonctions à valeur booléenne (`if est_pair(n): ...`) ou l'usage de variables booléennes (`if propriete: ...`). Il devrait être possible de mettre à l'épreuve cette idée en remplaçant une expression booléenne par un appel de fonction équivalent (et vice-versa) comme dans l'exemple ci-dessous.

```python
def estpronatif(x):
    if x < 0:
        return True
    else:
        return False
```

```python
def estpronatif(x):
    return x < 0
```

Ceci devrait permettre de faire apparaître des expressions qui « questionneront » les conceptions déjà présentes des élèves (`if estpronatif(x) == True: ...` remplacé par `if (x < 0) == True: ...`), mais cela reste à vérifier.

# 6. Archétypes de variables booléennes

On s'intéresse dans cette section à quelques cas d'usage typiques de variables booléennes, en s'inspirant de la catégorisation des rôles de variables par Samurçay (1985), enrichie par Sajaniemi (2002) et reprise par Sorva et al. (2007).

<footnote n="15">¹⁵Plus généralement, les consignes ou recommandations liées au style peuvent être difficiles à motiver auprès des élèves par des rétroactions directes du milieu, même quand elles sont issues de consensus d'experts en réponse à des nécessités pratiques. C'est le cas notamment pour les usages décrits par la « PEP 8 » déjà mentionnée (van Rossum et al., 2013).</footnote>
À titre d'exemple pour illustrer les différents usages, on étudie l'implémentation d'un prédicat `est_triee` qui vérifie qu'une liste est triée par ordre croissant. On considère qu'une liste à 0 ou 1 élément est triée. Mathématiquement, la liste L est triée si pour tous indices i et j valides avec $i \le j$ on a $L[i] \le L[j]$. Ceci peut s'exprimer de manière équivalente et disant que pour tout indice i on a $L[i] \le L[i+1]$.

Les exemples qui suivent sont formulés en Python et son type booléen, mais les mêmes observations s'appliquent dans n'importe quel langage même s'il n'a pas de type booléen. Par exemple, en Scratch, on pourra utiliser une variable numérique et la convention que la valeur 0 représente le faux et la valeur 1 représente le vrai.

Quelle que soit l'implémentation choisie, on souhaite que la fonction satisfasse les tests suivants :

```python
>>> est_triee([])
True
>>> est_triee([6])
True
>>> est_triee([2, 4, 7, 94])
True
>>> est_triee([6, 3, 9])
False
>>> est_triee([3, 9, 6])
False
```

Voici une écriture possible de cette fonction :

```python
def est_triee_v1(lst):
    for i in range(1, len(lst)):
        if lst[i - 1] > lst[i]:
            return False
    return True
```

On note l'usage de l'instruction `return` dans le corps de la boucle. D'autres implémentations possibles permettent d'illustrer quelques usages typiques des booléens.

## 6.1. Accumulateur booléen

Dans la version suivante, on utilise une variable booléenne comme *accumulateur* dont la valeur est mise à jour à chaque nouvelle itération de la boucle.

```python
def est_triee_v2(lst):
    resultat = True
    for i in range(1, len(lst)):
        resultat = resultat and lst[i - 1] <= lst[i]
    return resultat
```

Cette version revient à écrire :

```python
resultat = L[0] <= L[1] and L[1] <= L[2] and ... and L[n-2] <= L[n-1]
```

où n serait la longueur de la liste, et en remplaçant le ... par les comparaisons intermédiaires.<fnref n="16" />
En effet, la liste L est triée si et seulement si $L[0] \le L[1] \text{ et } L[1] \le L[2] \text{ et } \dots \text{ et } L[n-2] \le L[n-1]$

<footnote n="16">¹⁶Le ... cache un « et ainsi de suite » qui est le même que celui du 1+2+...+n qui représente la somme des n premiers entiers et qui se calcule avec un accumulateur numérique et des itérations.</footnote>
À titre d'exemple pour illustrer les différents usages, on étudie l'implémentation d'un prédicat `est_triee` qui vérifie qu'une liste est triée par ordre croissant. On considère qu'une liste à 0 ou 1 élément est triée. Mathématiquement, la liste L est triée si pour tous indices *i* et *j* valides avec $i \le j$ on a $L[i] \le L[j]$. Ceci peut s'exprimer de manière équivalente et disant que pour tout indice *i* on a $L[i] \le L[i+1]$.

Les exemples qui suivent sont formulés en Python et son type booléen, mais les mêmes observations s'appliquent dans n'importe quel langage même s'il n'a pas de type booléen. Par exemple, en Scratch, on pourra utiliser une variable numérique et la convention que la valeur 0 représente le faux et la valeur 1 représente le vrai.

Quelle que soit l'implémentation choisie, on souhaite que la fonction satisfasse les tests suivants :

```python
>>> est_triee([])
True
>>> est_triee([6])
True
>>> est_triee([2, 4, 7, 94])
True
>>> est_triee([6, 3, 9])
False
>>> est_triee([3, 9, 6])
False
```

Voici une écriture possible de cette fonction :

```python
def est_triee_v1(lst):
    for i in range(1, len(lst)):
        if lst[i - 1] > lst[i]:
            return False
    return True
```

On note l'usage de l'instruction `return` dans le corps de la boucle. D'autres implémentations possibles permettent d'illustrer quelques usages typiques des booléens.

## 6.1. Accumulateur booléen

Dans la version suivante, on utilise une variable booléenne comme *accumulateur* dont la valeur est mise à jour à chaque nouvelle itération de la boucle.

```python
def est_triee_v2(lst):
    resultat = True
    for i in range(1, len(lst)):
        resultat = resultat and lst[i - 1] <= lst[i]
    return resultat
```

Cette version revient à écrire :

`resultat = L[0] <= L[1] and L[1] <= L[2] and ... and L[n-2] <= L[n-1]`

où *n* serait la longueur de la liste, et en remplaçant le ... par les comparaisons intermédiaires.<fnref n="16" />
En effet, la liste *L* est triée si et seulement si $L[0] \le L[1] \text{ et } L[1] \le L[2] \text{ et } \dots \text{ et } L[n-2] \le L[n-1]$

<footnote n="16">¹⁶Le ... cache un « et ainsi de suite » qui est le même que celui du 1+2+...+n qui représente la somme des n premiers entiers et qui se calcule avec un accumulateur numérique et des itérations.</footnote>
puisque cette expression est fausse si et seulement si il existe un indice *i* tel que $L[i-1] \le L[i]$ est faux.

La propriété invariante dans cet exemple est que *resultat* vaut True si et seulement si la portion de la liste d'indices strictement inférieurs à *i* est triée. En sortie de boucle, on a donc bien la propriété souhaitée que *resultat* vaut True si et seulement si la liste entière est triée.

## 6.2. Drapeau à sens unique

Dans la variante qui suit, on utilise une variable booléenne `lst_est_triee`, utilisée comme drapeau (comme défini en section 3.2) mais ne contrôlant pas directement la continuation de la boucle. Cette variable vaut initialement True, et on lui donne la valeur False dès que deux éléments consécutifs mal ordonnés sont rencontrés dans la liste.

```python
def est_triee_v3(lst):
    lst_est_triee = True

    for i in range(1, len(lst)):
        if lst[i - 1] > lst[i]:
            lst_est_triee = False

    return lst_est_triee
```

On peut remarquer que la variable `lst_est_triee` dans cette fonction respecte le même invariant que *resultat* dans `est_triee_v2()`.

Il est important de remarquer que les fonctions `est_triee_v2()` et `est_triee_v3()` ne sont pas algorithmiquement équivalentes à `est_triee_v1()`. En effet, celles-ci parcourent toujours entièrement l'ensemble des indices quelles que soient les valeurs rencontrées,<fnref n="17" /> alors que `est_triee_v1()` s'interrompt dès qu'un cas d'inversion de l'ordre est rencontré.

## 6.3. Drapeau de continuation de boucle

Dans la version suivante de la fonction `est_triee()`, la variable booléenne continuer est utilisée comme un drapeau qui indique s'il faut continuer à répéter le corps de la boucle ou non. Dès que deux éléments consécutifs mal ordonnés sont rencontrés, la valeur de cette variable passe à False et la boucle se termine. Un soin particulier doit être apporté à la valeur de retour de la fonction.

```python
def est_triee_v4_errure(lst):
    continuer = True
    i = 1

    while i < len(lst) and continuer:
        continuer = lst[i - 1] <= lst[i]
        i += 1

    return i == len(lst)  # échec sur [] et [3, 9, 6]
```

La version ci-dessus échoue quand la première inversion se situe à la toute fin de la liste, car on a bien $i == len(lst)$ alors que la liste n'est pas triée. D'autre part, quand la liste est vide, le résultat est incorrect. On peut remédier à ce problème en remplaçant `return i == len(lst)` par `return continuer` par exemple.

On peut aussi omettre entièrement la variable continuer, comme dans la version suivante. Pour éviter l'erreur de la fonction précédente quand la liste est vide, il est nécessaire d'ajouter un premier test permettant de traiter ce cas particulier.

<footnote n="17">¹⁷Dans le cas de `est_triee_v2()`, même si l'ensemble des indices est parcouru entièrement, toutes les valeurs ne sont pas forcément examinées, du fait du caractère séquentiel de l'opérateur `and` expliqué plus loin.</footnote>
puisque cette expression est fausse si et seulement si il existe un indice *i* tel que $L[i-1] \le L[i]$ est faux.

La propriété invariante dans cet exemple est que *resultat* vaut True si et seulement si la portion de la liste d'indices strictement inférieurs à *i* est triée. En sortie de boucle, on a donc bien la propriété souhaitée que *resultat* vaut True si et seulement si la liste entière est triée.

## 6.2. Drapeau à sens unique

Dans la variante qui suit, on utilise une variable booléenne `lst_est_triee`, utilisée comme drapeau (comme défini en section 3.2) mais ne contrôlant pas directement la continuation de la boucle. Cette variable vaut initialement True, et on lui donne la valeur False dès que deux éléments consécutifs mal ordonnés sont rencontrés dans la liste.

```python
def est_triee_v3(lst):
    lst_est_triee = True

    for i in range(1, len(lst)):
        if lst[i - 1] > lst[i]:
            lst_est_triee = False

    return lst_est_triee
```

On peut remarquer que la variable `lst_est_triee` dans cette fonction respecte le même invariant que *resultat* dans `est_triee_v2()`.

Il est important de remarquer que les fonctions `est_triee_v2()` et `est_triee_v3()` ne sont pas algorithmiquement équivalentes à `est_triee_v1()`. En effet, celles-ci parcourent toujours entièrement l'ensemble des indices quelles que soient les valeurs rencontrées,<fnref n="17" /> alors que `est_triee_v1()` s'interrompt dès qu'un cas d'inversion de l'ordre est rencontré.

## 6.3. Drapeau de continuation de boucle

Dans la version suivante de la fonction `est_triee()`, la variable booléenne continuer est utilisée comme un drapeau qui indique s'il faut continuer à répéter le corps de la boucle ou non. Dès que deux éléments consécutifs mal ordonnés sont rencontrés, la valeur de cette variable passe à False et la boucle se termine. Un soin particulier doit être apporté à la valeur de retour de la fonction.

```python
def est_triee_v4_errure(lst):
    continuer = True
    i = 1

    while i < len(lst) and continuer:
        continuer = lst[i - 1] <= lst[i]
        i += 1

    return i == len(lst)  # échec sur [] et [3, 9, 6]
```

La version ci-dessus échoue quand la première inversion se situe à la toute fin de la liste, car on a bien $i == len(lst)$ alors que la liste n'est pas triée. D'autre part, quand la liste est vide, le résultat est incorrect. On peut remédier à ce problème en remplaçant `return i == len(lst)` par `return continuer` par exemple.

On peut aussi omettre entièrement la variable `continuer`, comme dans la version suivante. Pour éviter l'erreur de la fonction précédente quand la liste est vide, il est nécessaire d'ajouter un premier test permettant de traiter ce cas particulier.

<footnote n="17">¹⁷Dans le cas de `est_triee_v2()`, même si l'ensemble des indices est parcouru entièrement, toutes les valeurs ne sont pas forcément examinées, du fait du caractère séquentiel de l'opérateur `and` expliqué plus loin.</footnote>
```python
def est_triee_v5(lst):
    if len(lst) == 0:
        return True

    # dorénavant, on sait que len(lst) >= 1
    i = 1
    while i < len(lst) and lst[i - 1] <= lst[i]:
        i += 1

    # ici, on a i >= len(lst) ou lst[i - 1] > lst[i]
    return i == len(lst)
```

Dans l'exemple précédent on utilise aussi l'aspect séquentiel de `and` dans la condition de boucle (cf. sec. 5.2). Si $i < len(lst)$ vaut `False`, l'expression $lst[i - 1] <= lst[i]$ ne sera pas évaluée et il n'y aura pas de tentative d'accès à $lst[i]$ pour $i$ supérieur ou égal à la longueur de la liste, ce qui ferait planter le programme.

## 7. Conclusions et perspectives

Nous avons dans ce texte passé en revue différents statuts et usages des booléens en programmation, sous leur double statut de condition et de type de donnée. Dans tous les langages, différents opérateurs ainsi que des fonctions prédéfinies permettent de construire des expressions booléennes et d'autres fonctions booléennes. L'aspect séquentiel des opérateurs *et* *ou* dans la plupart des langages n'est pas anodin, et devra être explicité si les exemples de programmes étudiés en classe présentent cette caractéristique.

L'apparition d'écritures redondantes dans les programmes des élèves, même s'il n'est pas forcément souhaitable de le sanctionner, peut être le signe d'un manque de compréhension du statut des booléens. Au début de leur apprentissage en programmation, les élèves rencontrent les booléens comme conditions dans des structures conditionnelles et ces conditions ont le plus souvent la forme d'expression avec opérateur de comparaison. Il peut leur être alors difficile de percevoir les booléens comme type de données, les expressions conditionnelles comme expressions à valeur booléenne (que l'on peut donc affecter à une variable ou renvoyer comme résultat d'une fonction), et n'importe quelle expression à valeur booléenne comme une éventuelle condition. Ceci est d'autant plus vrai qu'en Scratch, les variables de type booléen n'existent pas.

Pour remédier à ces difficultés, il semble nécessaire de concevoir des séquences d'enseignement qui fassent apparaître aussi clairement que possible la nuance entre booléens en tant que données et booléens en tant que conditions, accompagnées d'exercices mettant l'accent sur les divers points évoqués précédemment (affectation de valeurs booléennes à des variables, renvoi de valeur booléenne par une fonction, etc.). Nous nous proposons de publier sur le site de la C3i<fnref n="18" />, au fur et à mesure de leur conception, divers contenus et réflexions directement utilisables en ce sens.

Finalement, en lien avec ce travail, il nous semble intéressant d'engager une réflexion sur l'enseignement de la preuve de correction de programmes, axée par exemple d'une part sur le concept de propriété et d'invariant de boucle et d'autre part sur la dynamique du programme et la notion de variant. Ceci nécessite un travail de transposition des concepts de la logique de Hoare à un niveau de première ou terminale, qui, à notre connaissance, ne semble pas avoir été développé.

## 8. Références

Barnes, D. J., & Shinners-Kennedy, D. (2011). A study of loop style and abstraction in pedagogic practice. *Proceedings of the Thirteenth Australasian Computing Education Conference - Volume*

<footnote n="18">¹⁸https://www.univ-irem.fr/-cii-informatique-</footnote>
```python
def est_triee_v5(lst):
    if len(lst) == 0:
        return True

    # dorénavant, on sait que len(lst) >= 1
    i = 1
    while i < len(lst) and lst[i - 1] <= lst[i]:
        i += 1

    # ici, on a i >= len(lst) ou lst[i - 1] > lst[i]
    return i == len(lst)
```

Dans l'exemple précédent on utilise aussi l'aspect séquentiel de `and` dans la condition de boucle (cf. sec. 5.2). Si $i < len(lst)$ vaut `False`, l'expression $lst[i - 1] <= lst[i]$ ne sera pas évaluée et il n'y aura pas de tentative d'accès à $lst[i]$ pour $i$ supérieur ou égal à la longueur de la liste, ce qui ferait planter le programme.

## 7. Conclusions et perspectives

Nous avons dans ce texte passé en revue différents statuts et usages des booléens en programmation, sous leur double statut de condition et de type de donnée. Dans tous les langages, différents opérateurs ainsi que des fonctions prédéfinies permettent de construire des expressions booléennes et d'autres fonctions booléennes. L'aspect séquentiel des opérateurs *et* et *ou* dans la plupart des langages n'est pas anodin, et devra être explicité si les exemples de programmes étudiés en classe présentent cette caractéristique.

L'apparition d'écritures redondantes dans les programmes des élèves, même s'il n'est pas forcément souhaitable de le sanctionner, peut être le signe d'un manque de compréhension du statut des booléens. Au début de leur apprentissage en programmation, les élèves rencontrent les booléens comme conditions dans des structures conditionnelles et ces conditions ont le plus souvent la forme d'expression avec opérateur de comparaison. Il peut leur être alors difficile de percevoir les booléens comme type de données, les expressions conditionnelles comme expressions à valeur booléenne (que l'on peut donc affecter à une variable ou renvoyer comme résultat d'une fonction), et n'importe quelle expression à valeur booléenne comme une éventuelle condition. Ceci est d'autant plus vrai qu'en Scratch, les variables de type booléen n'existent pas.

Pour remédier à ces difficultés, il semble nécessaire de concevoir des séquences d'enseignement qui fassent apparaître aussi clairement que possible la nuance entre booléens en tant que données et booléens en tant que conditions, accompagnées d'exercices mettant l'accent sur les divers points évoqués précédemment (affectation de valeurs booléennes à des variables, renvoi de valeur booléenne par une fonction, etc.). Nous nous proposons de publier sur le site de la C3i<fnref n="18" />, au fur et à mesure de leur conception, divers contenus et réflexions directement utilisables en ce sens.

Finalement, en lien avec ce travail, il nous semble intéressant d'engager une réflexion sur l'enseignement de la preuve de correction de programmes, axée par exemple d'une part sur le concept de propriété et d'invariant de boucle et d'autre part sur la dynamique du programme et la notion de variant. Ceci nécessite un travail de transposition des concepts de la logique de Hoare à un niveau de première ou terminale, qui, à notre connaissance, ne semble pas avoir été développé.

## 8. Références

Barnes, D. J., & Shinners-Kennedy, D. (2011). A study of loop style and abstraction in pedagogic practice. *Proceedings of the Thirteenth Australasian Computing Education Conference - Volume*

<footnote n="18">¹⁸https://www.univ-irem.fr/-cii-informatique-</footnote>
114, 29-36.

Beffara, E., More, M., & Prouteau, C. (2017). *Algorithmique et programmation au cycle 4 : Commentaires et recommandations du groupe Informatique de la CII Lycée*. Institut de recherche pour l'enseignement des mathématiques.

Ben-Ari, M. (1996). Structure exits, not loops. *ACM SIGCSE Bulletin*, 28(3), 51. https://doi.org/10.1145/234867.234878

Commission Inter-Irem Informatique (C3I). (2020). *Petit glossaire de termes informatiques*. FramaGit. https://framagit.org/c3i/glossaire/-/wikis/home

Dijkstra, E. W. (1968). Letters to the editor: go to statement considered harmful. *Communications of the ACM*, 11(3), 147-148. https://doi.org/10.1145/362929.362947

Lagrange, J.-B., & Rogalski, J. (2017). Savoirs, concepts et situations dans les premiers apprentissages en programmation et en algorithmique. *Annales de Didactiques et de Sciences Cognitives*, 41. https://hal.archives-ouvertes.fr/hal-01740442

Roberts, E. S. (1995). Loop exits and structured programming: reopening the debate. *ACM SIGCSE Bulletin*, 27(1), 268-272. https://doi.org/10.1145/199691.199815

Sajaniemi, J. (2002). An empirical analysis of roles of variables in novice-level procedural programs. *Proceedings IEEE 2002 Symposia on Human Centric Computing Languages and Environments*, 37-39. https://doi.org/10.1109/HCC.2002.1046340

Samurçay, R. (1985). Signification et fonctionnement du concept de variable informatique chez des élèves débutants. *Educational Studies in Mathematics*, 16(2), 143-161. https://doi.org/10.1007/PL00020737

Sorva, J., Karavirta, V., & Korhonen, A. (2007). Roles of Variables in Teaching. *Journal of Information Technology Education: Research*, 6, 407-423. https://doi.org/10.28945/224

van Rossum, G., Warsaw, B., & Coghlan, A. (2013). *PEP 8 – Style Guide for Python Code*. Python Enhancement Proposals (PEPs). https://peps.python.org/pep-0008/

# 9. Annexes

## 9.1. Les booléens dans différents langages

<table><thead><tr><td>Langage</td><td>Remarque</td><td>Interprété comme vrai</td><td>Interprété comme faux</td></tr></thead><tbody><tr><td>Scratch</td><td>Possède des valeurs booléennes mais pas de blocs pour exprimer directement les valeurs <i>vrai</i> ou <i>faux</i></td><td>les expressions booléennes vraies</td><td>les expressions booléennes fausses</td></tr><tr><td>Python</td><td>Possède un type booléen mais d'autres valeurs peuvent être interprétées comme des booléens</td><td>True, nombre non nul, liste ou chaîne non vide, etc.</td><td>False, 0, 0.0, liste ou chaîne vide, None, etc.</td></tr><tr><td>C (avant C99)</td><td>ne possède pas de type booléen et utilise des valeurs entières.</td><td>1 ou toute valeur non nulle</td><td>0, ou toute valeur nulle</td></tr><tr><td>C (à partir de C99)<sup>19</sup></td><td>Un type <i>bool</i> explicite existe depuis la version C99 du langage C</td><td>comme en C, ou true</td><td>comme en C, ou false</td></tr><tr><td>Ocaml</td><td>Possède un type <i>bool</i> primitif</td><td>true</td><td>false</td></tr><tr><td>Haskell</td><td>Possède un type <i>Bool</i></td><td>True</td><td>False</td></tr></tbody></table>

<footnote n="19">¹⁹Grâce à l'en-tête stdbool.h, voir par exemple ce paragraphe sur Wikipedia : https://fr.wikipedia.org/wiki/Bool%C3%A9en#C,_C++,_Objective-C,_AWK.</footnote>
114, 29-36.

Beffara, E., More, M., & Prouteau, C. (2017). *Algorithmique et programmation au cycle 4 : Commentaires et recommandations du groupe Informatique de la CII Lycée*. Institut de recherche pour l'enseignement des mathématiques.

Ben-Ari, M. (1996). Structure exits, not loops. *ACM SIGCSE Bulletin*, 28(3), 51. https://doi.org/10.1145/234867.234878

Commission Inter-Irem Informatique (C3I). (2020). *Petit glossaire de termes informatiques*. Framagit. https://framagit.org/c3i/glossaire/-/wikis/home

Dijkstra, E. W. (1968). Letters to the editor: go to statement considered harmful. *Communications of the ACM*, 11(3), 147-148. https://doi.org/10.1145/362929.362947

Lagrange, J.-B., & Rogalski, J. (2017). Savoirs, concepts et situations dans les premiers apprentissages en programmation et en algorithmique. *Annales de Didactiques et de Sciences Cognitives*, 41. https://hal.archives-ouvertes.fr/hal-01740442

Roberts, E. S. (1995). Loop exits and structured programming: reopening the debate. *ACM SIGCSE Bulletin*, 27(1), 268-272. https://doi.org/10.1145/199691.199815

Sajaniemi, J. (2002). An empirical analysis of roles of variables in novice-level procedural programs. *Proceedings IEEE 2002 Symposia on Human Centric Computing Languages and Environments*, 37-39. https://doi.org/10.1109/HCC.2002.1046340

Samurçay, R. (1985). Signification et fonctionnement du concept de variable informatique chez des élèves débutants. *Educational Studies in Mathematics*, 16(2), 143-161. https://doi.org/10.1007/PL00020737

Sorva, J., Karavirta, V., & Korhonen, A. (2007). Roles of Variables in Teaching. *Journal of Information Technology Education: Research*, 6, 407-423. https://doi.org/10.28945/224

van Rossum, G., Warsaw, B., & Coghlan, A. (2013). *PEP 8 – Style Guide for Python Code*. Python Enhancement Proposals (PEPs). https://peps.python.org/pep-0008/

# 9. Annexes

## 9.1. Les booléens dans différents langages

<table><thead><tr><td>Langage</td><td>Remarque</td><td>Interprété comme vrai</td><td>Interprété comme faux</td></tr></thead><tbody><tr><td>Scratch</td><td>Possède des valeurs booléennes mais pas de blocs pour exprimer directement les valeurs <i>vrai</i> ou <i>faux</i></td><td>les expressions booléennes vraies</td><td>les expressions booléennes fausses</td></tr><tr><td>Python</td><td>Possède un type booléen mais d'autres valeurs peuvent être interprétées comme des booléens</td><td>True, nombre non nul, liste ou chaîne non vide, etc.</td><td>False, 0, 0.0, liste ou chaîne vide, None, etc.</td></tr><tr><td>C (avant C99)</td><td>ne possède pas de type booléen et utilise des valeurs entières.</td><td>1 ou toute valeur non nulle</td><td>0, ou toute valeur nulle</td></tr><tr><td>C (à partir de C99)<sup>19</sup></td><td>Un type <i>bool</i> explicite existe depuis la version C99 du langage C</td><td>comme en C, ou true</td><td>comme en C, ou false</td></tr><tr><td>Ocaml</td><td>Possède un type <i>bool</i> primitif</td><td>true</td><td>false</td></tr><tr><td>Haskell</td><td>Possède un type <i>Bool</i></td><td>True</td><td>False</td></tr></tbody></table>

<footnote n="19">¹⁹Grâce à l'en-tête `stdbool.h`, voir par exemple ce paragraphe sur Wikipedia : https://fr.wikipedia.org/wiki/Bool%C3%A9en#C,_C++,_Objective-C,_AWK.</footnote>
<table><thead><tr><th>Langage</th><th>Remarque</th><th>Interprété comme <i>vrai</i></th><th>Interprété comme <i>faux</i></th></tr></thead><tbody><tr><td>Bash</td><td>Interprète le code de retour des commandes comme des valeurs de vérité</td><td>code de terminaison<br>0 (renvoyé par la commande <i>true</i>)</td><td>code de terminaison<br>non nul (la commande <i>false</i> renvoie 1)</td></tr><tr><td>Java</td><td>Possède un type primitif <i>boolean</i><sup>20</sup></td><td><i>true</i></td><td><i>false</i></td></tr><tr><td>Javascript</td><td>Possède des valeurs booléennes primitives<sup>21</sup> et pratique des conversions implicites</td><td><i>true</i></td><td><i>false</i></td></tr></tbody></table>

## 9.2. Les conversions de type en Python

Le tableau ci-dessous synthétise une partie des conversions de type impliquant les booléens, soit comme type de départ soit comme type final.

### Conversions depuis le type bool

<table><thead><tr><th>valeur de départ</th><th>commande</th><th>type d'arrivée</th><th>valeur d'arrivée</th></tr></thead><tbody><tr><td>True</td><td>str(True)</td><td>str</td><td>'True'</td></tr><tr><td>False</td><td>str(False)</td><td>str</td><td>'False'</td></tr><tr><td>True</td><td>int(True)</td><td>int</td><td>1</td></tr><tr><td>False</td><td>int(False)</td><td>int</td><td>0</td></tr><tr><td>True</td><td>float(True)</td><td>float</td><td>1.0</td></tr><tr><td>False</td><td>float(False)</td><td>float</td><td>0.0</td></tr></tbody></table>

### Conversions vers le type bool

<table><thead><tr><th>type de départ</th><th>valeur de départ</th><th>commande</th><th>valeur d'arrivée</th></tr></thead><tbody><tr><td>str</td><td>la chaine vide</td><td>bool('')</td><td>False</td></tr><tr><td>str</td><td>toute chaine non vide</td><td>bool('courgette')</td><td>True</td></tr><tr><td>int</td><td>tout entier non nul</td><td>bool(-7)</td><td>True</td></tr><tr><td>int</td><td>0</td><td>bool(0)</td><td>False</td></tr><tr><td>float</td><td>tout flottant non nul</td><td>bool(0.5)</td><td>True</td></tr><tr><td>float</td><td>0.0</td><td>bool(0.0)</td><td>False</td></tr><tr><td>List</td><td>liste non vide</td><td>bool([0,1,3])</td><td>True</td></tr><tr><td>List</td><td>liste vide []</td><td>bool([])</td><td>False</td></tr></tbody></table>

<footnote n="20"><sup>20</sup>Voir la documentation. Il existe également un type objet correspondant Boolean, comme pour la plupart des autres types primitifs.</footnote>

<footnote n="21"><sup>21</sup>Le standard ECMAScript stipule l'existence d'un type primitif Boolean, voir aussi sur MDN. Comme en Java, il existe aussi un type *wrapper* (objet) Boolean.</footnote>
<table><thead><tr><th>Langage</th><th>Remarque</th><th>Interprété comme<br>vrai</th><th>Interprété comme<br>faux</th></tr></thead><tbody><tr><td>Bash</td><td>Interprète le code de retour des<br>commandes comme des valeurs de<br>vérité</td><td>code de terminaison<br>0 (renvoyé par la<br>commande true)</td><td>code de terminaison<br>non nul (la commande<br>false renvoie 1)</td></tr><tr><td>Java</td><td>Possède un type primitif boolean<sup>20</sup></td><td>true</td><td>false</td></tr><tr><td>Javascript</td><td>Possède des valeurs booléennes<br>primitives<sup>21</sup> et pratique des<br>conversions implicites</td><td>true</td><td>false</td></tr></tbody></table>

## 9.2. Les conversions de type en Python

Le tableau ci-dessous synthétise une partie des conversions de type impliquant les booléens, soit comme type de départ soit comme type final.

### Conversions depuis le type bool

<table><thead><tr><th>valeur de départ</th><th>commande</th><th>type d'arrivée</th><th>valeur d'arrivée</th></tr></thead><tbody><tr><td>True</td><td>str(True)</td><td>str</td><td>'True'</td></tr><tr><td>False</td><td>str(False)</td><td>str</td><td>'False'</td></tr><tr><td>True</td><td>int(True)</td><td>int</td><td>1</td></tr><tr><td>False</td><td>int(False)</td><td>int</td><td>0</td></tr><tr><td>True</td><td>float(True)</td><td>float</td><td>1.0</td></tr><tr><td>False</td><td>float(False)</td><td>float</td><td>0.0</td></tr></tbody></table>

### Conversions vers le type bool

<table><thead><tr><th>type de départ</th><th>valeur de départ</th><th>commande</th><th>valeur d'arrivée</th></tr></thead><tbody><tr><td>str</td><td>la chaine vide</td><td>bool("")</td><td>False</td></tr><tr><td>str</td><td>toute chaine non vide</td><td>bool('courgette')</td><td>True</td></tr><tr><td>int</td><td>tout entier non nul</td><td>bool(-7)</td><td>True</td></tr><tr><td>int</td><td>0</td><td>bool(0)</td><td>False</td></tr><tr><td>float</td><td>tout flottant non nul</td><td>bool(0.5)</td><td>True</td></tr><tr><td>float</td><td>0.0</td><td>bool(0.0)</td><td>False</td></tr><tr><td>List</td><td>liste non vide</td><td>bool([0,1,3])</td><td>True</td></tr><tr><td>List</td><td>liste vide []</td><td>bool([])</td><td>False</td></tr></tbody></table>

<footnote n="20">20 Voir la documentation. Il existe également un type objet correspondant Boolean, comme pour la plupart des autres types primitifs.</footnote>

<footnote n="21">21 Le standard ECMAScript stipule l'existence d'un type primitif Boolean, voir aussi sur MDN. Comme en Java, il existe aussi un type wrapper (objet) Boolean.</footnote>