Introduction à la programmation : Les bases du langage Racket (2)

Nous allons das ce chapitre continuer notre exploration des bases du langage Racket. Nous terminerons notre discussion sur les opérations sur les listes, nous verrons ensuite ce que sont les prédicats et les expressions conditionnelles. Enfin, nous aborderons la notion de fonction, et nous serons fin prêts à écrire de petits programmes en Racket.

Pour tout ce qui suit, nous utiliserons le langage Racket dans DrRacket (et non un des langages d’enseignement). Veillez donc à le sélectionner comme nous l’avons fait dans le chapitre précédent.

Ceci étant fait, nous pouvons commencer !

Quelques opérations sur les listes

Nous savons comment construire une liste de façon primitive — c’est-à-dire en voyant la liste selon sa représentation en mémoire — en utilisant les cellules cons. Nous savons également récupérer les éléments d’une liste en utilisant les pointeurs CAR et CDR. Dans notre étude de ces opérations élémentaires, vous avez dû vous rendre compte qu’il était laborieux d’extraire les éléments d’une liste en chaînant les opérations CAR et CDR et encore plus laborieux de construire une liste en chaînant des cellules cons. Comme nous avons eu l’habitude de le faire en mathématiques, nous sommes tentés de rechercher des opérateurs raccourcis pour obtenir les mêmes résultats. Heureusement, les concepteurs de Racket y ont pensé et nous ont offert ces opérateurs.

Construction d’une liste avec les opérateurs LIST et APPEND

Plutôt que d’utiliser l’opération CONS pour construire une liste, on peut utiliser l’opérateur LIST. Cet opérateur abstrait juste le chaînage d’opérateurs CONS. Du coup, à partir des éléments ‘racket, 51, “LISP”, ‘(0.2 72 “nombres”), on peut créer une liste dans le même ordre simplement en les passant comme arguments à l’opérateur :

> (list ‘racket 51 “LISP” ‘(0.2 72 “nombres”))
\(=>\) ‘(racket 51 “LISP” (0.2 72 “nombres”))

Nous voyez bien que nous avons obtenu la liste ‘(racket 51 “LISP” (0.2 72 “nombres”)). Remarquez que nous aurions pu obtenir le même en chaînant des CONS :

> (cons 'racket 
        (cons 51 
              (cons "LISP" 
                    (cons 
                      (cons 0.2 
                            (cons 72 
                                  (cons "nombres" '()))) '()))))

 \(=>\) '(racket 51 "LISP" (0.2 72 "nombres"))

On voit aisément comment le chaînage est laborieux. L’opérateur LIST est plus haut niveau car il donne l’impression à l’utilisateur de construire la liste en un seul coup. Retenez que ceci est juste une abstraction syntaxique et qu’en réalité LIST fait un chaînage de CONS.

On peut aussi construire une liste en utilisant l’opérateur APPEND. Cet opérateur associe deux listes pour en former une seule : on dit qu’il réalise une opération de concaténation. Par exemple :

> (append ‘(“blog”) ‘(“des” “codeurs”))
\(=>\) ‘(“blog” “des codeurs”)

Le REPL accepte cette expression parce que les deux arguments sont des listes. En effet, dans le cas de CONS, le premier argument n’aurait pas été une liste comme ci-après :

> (cons “blog” ‘(“des” “codeurs”))
\(=>\) ‘(“blog” “des codeurs”)

On obtient le même résultat, sauf que CONS est plus simple. Voici comment j’obtiens la liste ‘(racket 51 “LISP” (0.2 72 “nombres”)) en utilisant APPEND:

> (append '('racket) 
          (append '(51) 
                  (append '("LISP") 
                          (append 
                            (append '(0.2) 
                                    (append '(72) 
                                            (append '("nombres") '()))) '()))))

 \(=>\) '(racket 51 "LISP" (0.2 72 "nombres"))

Je sais ! vous vous dites qu’APPEND est inutile puisqu’il fait en plus compliqué ce que CONS aurait fait facilement. Vous avez raison, mais APPEND n’a pas été conçu pour cet usage. APPEND est utile pour ajouter des éléments à la fin d’une liste : ce qui est impossible avec CONS. Par exemple, si j’ai la liste ‘(1 2) et que je veux obtenir la liste ‘(1 2 3) à partir de cette liste, il impossible de le faire avec CONS car cet opérateur n’ajoute des élément qu’en tête de liste. C’est là qu’APPEND est supérieur, voici comment il le fait :

> (append ‘(1 2) ‘(3))
\(=>\) ‘(1 2 3)

Je profite pour faire une remarque assez importante concernant les symboles dans les listes. Il n’est pas nécessaire de quoter les symboles lorsque la liste dans laquelle ils se trouvent est déjà quotée. Au lieu d’écrire ‘(1 2 ‘racket), il faut simplement écrire ‘(1 2 racket).

Les opérateurs FIRST et REST à la place de CAR et CDR

CAR et CDR ont des synonymes : FIRST et REST. FIRST fait exactement la même chose que CAR, REST fait pareil que CDR. Alors, quelle est l’utilité de ces nouveaux opérateurs ? Ceux qui comprennent l’anglais ont dû remarquer que first signifie “premier” et rest signifie “reste”. Les noms de ces opérateurs sont donc plus précis que les noms CAR et CDR. En effet ces derniers noms ont une raison historique, c’est pourquoi ils sont le plus souvent utilisés. Et ils sont nécessairement plus rapides que FIRST et REST, puisqu’ils sont plus bas niveau. Si toutefois, nous sommes intéressés par la précision, il est mieux d’employer FIRST et REST. Le choix vous incombe donc.

Par ailleurs, Racket implémente des opérateurs raccourcis pour récupérer jusqu’aux dix premiers éléments d’une liste. Ce sont : first, second, third, fourth, fifth, sixth, seventh, eigth, ninth and tenth. Par exemple :

> (second ‘(riz avocat prune tomate oignon ail courgette))
\(=>\) ‘riz
> (fifth ‘(riz avocat prune tomate oignon ail courgette))
\(=>\) ‘oignon
> (seventh ‘(riz avocat prune tomate oignon ail courgette))
\(=>\) ‘courgette

L’opérateur LENGTH pour obtenir le nombre d’éléments d’une liste

Vous vous êtes certainement demandé comment connaître le nombre d’éléments d’une liste donnée. En fait, si la liste est courte, vous voyez immédiatement combien d’éléments elle comporte, mais ça devient plus difficile si la liste est très longue. Dans tous les cas, l’ordinateur, lui, n’a aucun moyen de connaître le nombre d’éléments d’une liste si on ne lui en donne le moyen. L’opérateur LENGTH a été justement créé pour ça. Il prend une liste en argument et retourne la taille (ou la longueur) de cette liste, c’est-à-dire son nombre d’éléments. Voici quelques exemples :

> (length ‘(riz avocat prune tomate oignon ail courgette))
\(=>\) 7
> (length ‘(() () (())))
\(=>\) 3
> (length ‘())
\(=>\) 0
> (length ‘(riz (avocat prune) tomate (oignon) (ail (courgette)))
\(=>\) 5

Pour comprendre les résultats précédents, remarquez qu’une liste vide dans une liste est un élément, qu’une liste vide ne contient aucun élément (donc zéro élément) et qu’en comptant les éléments d’une liste, on ne prend pas en compte les sous-listes d’un élément de type liste.

Les prédicats et expressions conditionnelles en Racket

Les prédicats en Racket

Nous avons vu dans le chapitre précédent qu’une proposition est une expression qui est soit vraie soit fausse : on dit que sa valeur est booléenne. Un prédicat est un opérateur qui teste la valeur d’une proposition : il retourne donc #true (ou #t) si la proposition est vraie et #false (ou #f) si la proposition est fausse. Racket nous offre un grand nombre de prédicats, tels que odd?, even? et number? que nous avons vu dans le chapitre précédent. En effet, une convention en Racket est de terminer le nom des prédicats par un point d’interrogation, qui montre que nous posons une question. Par exemple (odd? 2) signifie “le nombre 2 est-il impair ?”. Puisque 2 est impair, le REPL retournera #f.

Il y a presqu’un prédicat associé à chaque type, cela permet de vérifier si une donnée est d’un certain type. Par exemple number?, list?, integer?, string?, symbol? sont de prédicats qui permettent de tester si la donnée qu’on leur passe en argument est un nombre (quelconque), une liste, un nombre entier, une chaîne de caractères et un symbole, respectivement. Cette liste n’est pas exhaustive mais soyez certains qu’à chaque type de donnée, il y a un prédicat. Nous prendrons aussi l’habitude de développer des prédicats pour les structures de données que nous développerons.

L’un des prédicats les plus primitifs en LISP est le prédicat eq?, qui permet de tester l’identité de deux données. Si les deux données font référence à exactement la même donnée en mémoire, le prédicat retourne #t, sinon il retourne #f.

> (eq? ‘blogdescodeurs ‘blogdescodeurs)
\(=>\) #t
> (eq? ‘chat ‘CHAT)
\(=>\) #f

Quoique ce prédicat puisse être utilisé pour tester des données de tout type, il a été conçu pour ne tester que les symboles. Racket ne garantit pas que le résultats de tests réalisés sur des données d’autres types, seront corrects. En effet, si on veut tester si les chaînes de caractères “football” et “soccer” sont égales, plutôt que d’utiliser eq?, il convient d’utiliser le prédicat string=?. Pour tester si deux caractères sont égaux, on utilise le prédicat char=?.

> (string=? “football” “soccer”)
\(=>\) #f
> (char=? #\a #\A)
\(=>\) #f
> (eq? ‘chat ‘CHAT)
\(=>\) #f

Pour tester l’égalité de deux nombres, on utiliser le prédicat =, qui est simplement le symbole égal que nous avons vu en maths, sans le terminer d’un point d’interrogation.

> (= 4 5)
\(=>\) #f
> (= 1.0 1)
\(=>\) #t

Ce prédicat est beaucoup trop général au point qu’il ne fait pas la distinction entre les différents type de nombres. C’est pourquoi il estime que 1.0 est égal à 1. Dans certaines circonstances, on souhaiterait que retourner #t lorsque les nombres sont généralement égaux et s’ils sont de même nature, c’est-à-dire soit entiers, soit flottant, … On utilise pour cela le prédicat equal? qui est beaucoup plus strict :

> (equal? 1.0 1)
\(=>\) #f
> (equal? 1 1)
\(=>\) #t

On voit bien que dans ce cas, 1.0 n’est plus égal à 1 car 1.0 est un nombre flottant (à virgule) et 1 est un nombre entier naturel.

il y a aussi des prédicats pour tester l’inégalité de deux nombres, ce sont : >, <, >= et <=. Le prédicat > permet de tester si le premier nombre est plus grand que le second ; le prédicat < permet de tester si le premier nombre est plus petit que le second ; le prédicat >= permet de tester si le premier nombre est plus grand ou égal au second ; et le prédicat <= permet de tester si le premier nombre est plus petit ou égal au second. Voici quelques exemples d’utilisation de ces prédicats :

> (< 4 5)
\(=>\) #t
> (> 2.5 5)
\(=>\) #f
> (>= 4.0 4)
\(=>\) #t
> (<= 25 15)
\(=>\) #f

Les expressions conditionnelles

Puisque dans la vie courante, nous sommes souvent amenés à prendre des décisions basés sur l’évaluation de certaines propositions. Par exemple, s’il pleut on va à l’école, sinon on reste à la maison. Ce situations sont considérées en programmation, et sont représentées sous la forme de ce qu’on appelle les expressions conditionnelles. C’est-à-dire des expressions basées sur la valeur de vérité de certaines conditions. Nous allons voir, dans la suite, les expressions conditionnelles offertes pas Racket.

Les expressions conditionnelles IF, WHEN et UNLESS

Lorsque la décision à prendre est basée sur la véracité ou la fausseté d’une condition, on utilise l’expression conditionnelle IF (si, en français). Voici la syntaxe de IF :

(if condition? alors-decision1 sinon-decision2)

Si le prédicat condition? retourne #t alors on exécute la décision 1, sinon (si le prédicat retourne #f) on exécute la décision 2. Par exemple :

> (if (odd? 2) “Ce nombre est impair” “Ce nombre est pair”)
\(=>\) “Ce nombre est pair”
> (if (zero? 0) 0 “Nombre non nul”)
\(=>\) 0

Dans le premier exemple, puisque 2 n’est pas un nombre impair, c’est la décision 2 qui est évaluée. Alors que dans le second exemple, c’est la décision 1 qui est évaluée car la condition(zero? 0) est trivialement vraie.

Si nous n’avons qu’une seule décision et que celle-ci doit être exécutée lorsque le prédicat est vraie, on utilise le prédicat WHEN (quand, en français) au lieu de IF. Voici sa syntaxe :

(when condition? alors-decision)

Si la condition et vraie, on évalue la décision sinon on ne fait rien. Par exemple :

> (when (odd? 5) “Ce nombre est impair)
\(=>\) “Ce nombre est impair”
> (when (zero? 3) 0)
>

Si nous n’avons qu’une seule décision et que celle-ci doit être exécutée lorsque le prédicat est faux, on utilise le prédicat UNLESS (à moins que, en français) au lieu de IF. Voici sa syntaxe :

(unless condition? alors-decision)

Si la condition et fausse, on évalue la décision sinon on ne fait rien. Par exemple :

> (unless (odd? 4) “Ce nombre est pair)
\(=>\) “Ce nombre est impair”
> (unless (zero? 0) ‘Non-nul)
>

L’expression conditionnelle générale COND

Il est fort probable que vous vous posiez la question : “Et si j’ai plusieurs décisions à prendre selon plusieurs conditions ?” Racket contient exactement ce qu’il faut pour répondre à cette question. C’est l’opérateur COND. Sa syntaxe est la suivante :

(cond [cond1 decision1] [cond2 decision2] … [condN decisionN])

Chaque paire [cond decision] est appelée clause. Cette expression est équivalente à l’expression :

(if cond1 decision1 (if cond2 decision2 (… decisionN)))

Si cond1 est vraie, on exécute decision1 et on a fini. Sinon si cond2 est vraie, on exécute decision2, et on a fini. Sinon on continue jusqu’au dernier prédicat predN. Si celui-ci est vraie, on exécute decisionN sinon on ne fait rien.

En général, on met une dernière clause dont la décision se réalisera au cas où toute les autres clauses n’auraient pas vu leurs décisions exécutées. Pour cette clause, on utilise comme condition le symbole else (sans quote) ou le symbole #t. La syntaxe de COND devient donc :

(cond [cond1 decision1] [cond2 decision2] … [else decisionN])

Ou sous sa forme indentée :

(cond [cond1 decision1]
      [cond2 decision2]
      ...
      [else decisionN])

> (cond [(odd? 2) “Ce nombre est impair”] [(even? 2) “Ce nombre est pair”])
\(=>\) “Ce nombre est pair”

> (cond [(zero? 1) "Nombre nul"]
        [(even? 1) "Nombre pair"]
        [else "Nombre quelconque"])

\(=>\) “Nombre quelconque”

Ces exemples sur les expressions sont beaucoup trop simples et presqu’inutiles. Mon seul but est de vous montrer comment on les utilise. Nous verrons plus tard leur réelle utilité.

Les définitions de variables et de fonctions en Racket

La définition de variables en Racket

Nous avons vu dans le chapitre précédent ce qu’est une variable et quelles sont ses principales caractéristiques. Pour définir une variable en Racket, c’est-à-dire associer un identificateur à une valeur, on utilise la forme suivante :

(define id-var valeur)

define est l’opérateur qui se charge d’effectuer l’association de l’identificateur “id-var” et de la valeur “valeur”. Dès que l’association est faite, utiliser “id-var” ailleurs dans le code, c’est faire référence à sa valeur.

“id-var” est un symbole : il respecte donc toutes les règles que nous avons données sur les symboles, mais il ne doit pas être quoté lors de la définition. “valeur” peut être une forme quelconque. Par exemple :

> (define nom-admin-blogdescodeurs “Le messie”)
> (define age-admin-blogdescodeurs 27)
> (define nombre-inscrits-blogdescodeurs
(+ 150 52))

Nous venons de définir trois variables. Nous pouvons donc demander au REPL de nous retourner leurs valeurs :

> nom-admin-blogdescodeurs
\(=>\) “Le messie”
> age-admin-blogdescodeurs
\(=>\) 27
> nombre-inscrits-blogdescodeurs
\(=>\) 202

Remarquez bien que nous n’avons pas quoté les symboles et le REPL a évalué la forme (+ 150 52) que nous lui avions passée lors de la définition de la dernière variable. En effet, le REPL évalue toutes les formes qu’on lui donne. Si vous le voulez pas qu’il évalue une forme, quotez-la. Par exemple :

> ‘nom-admin-blogdescodeurs
\(=>\) ‘nom-admin-blogdescodeurs
> ‘(+ 150 52)
\(=>\) ‘(+ 150 52)

Comme je l’ai dit plus haut, on peut réutiliser les variables, déjà définies, dans de nouvelles définitions ou expressions. Le REPL prendra le soin de remplacer l’identificateur par sa valeur avant d’évaluer la forme.

> (+ age-admin-blogdescodeurs 1)
\(=>\) 28
> (define nombre-abonnes-blogdescodeurs
(- nombre-inscrits-blogdescodeurs 1)
> nombre-abonnes-blogdescodeurs
\(=>\) 201

La définition de fonctions en Racket

En programmation, la fonction est le premier élément d’abstraction. On peut dire que la fonction établit une abstraction de premier niveau (ou de première octave 😆 ). Expliquons la raison d’être du concept de fonction.

Imaginez que vous êtes caissière (ou caissier) dans un supermarché et que comme c’est la période des soldes, tous chacun de vos produits est vendu avec une réduction à hauteur du tiers du prix du produit. Donc un produit qui coûte normalement 45 francs, verra son prix réduit de \(45 \times \frac{1}{3} = 15\) francs, et donc il sera vendu à \(45 – 15 = 30\) francs.
Le premier client achète un produit de 60 francs, vous faites la réduction de \(60 \times \frac{1}{3} = 20\) francs, et donc il devra payer un montant de \(60 – 20 = 40\) francs. Le second client achète un produit de 84 francs, vous calculez la réduction qui est de \(84 \times \frac{1}{3} = 28\) francs, et donc il devra payer un montant de \(84 – 28 = 56\) francs. Le troisième client achète un produit de 117 francs, vous calculez la réduction qui est de \(117 \times \frac{1}{3} = 39\) francs, et donc il devra payer un montant de \(117 – 39= 78\) francs.
À ce stade, vous marquez une pause et vous vous en rendez compte que vous ne faites que répéter les mêmes opérations avec une donnée qui ne change pas — le taux de réduction — et une donnée qui peut varier à chaque achat — le prix du produit. Puisque vous savez que votre ordinateur peut vous aider à faire ces opérations rapidement, vous décidez de concevoir un code pour automatiser cette tâche. Voici comment vous formulez votre problème : “écrire un programme qui pour chaque valeur du produit, et en connaissance du taux de remise, calculera la remise à appliquer et le prix final à payer par le client“. Le programme que vous cherchez à écrire est une fonction, une opération pour généraliser la suite des opérations que vous aurez effectuées sans elle. Voici comment le concept de fonction naît naturellement de la nécessité d’automatiser une tâche répétitive dans laquelle seulement quelques données varient.

La syntaxe de base d’une fonction Racket est la suivante :

(define (nom-de-la-fonction param1 param2 … paramN) form1 form2 … formN)

Ou dans sa version indentée :

(define (nom-de-la-fonction param1 param2 ... paramN)
   form1
   form2
   ...
   formN)

nom-de-la-fonction est l’identificateur de la fonction, c’est un symbole ; param1, param2, …, paramN sont appelés paramètres de la fonction : ce sont les données variables de la fonction ; form1, form2, …, formN sont des formes Racket : ce sont des opérations à exécuter dans l’ordre pour aboutir au résultat recherché.

Puisque le but d’une fonction est de résoudre un problème spécifique, formN doit généralement être l’opération dont la valeur est le résultat cherché. Car c’est cette forme qui sera exécutée en dernier par le REPL et le résultat sera affiché.

Maintenant que nous savons connaissons la syntaxe des fonctions Racket, nous pouvons réfléchir sur la conception de notre petite fonction de caisse. On appellera la fonction calcul-prix-net. Elle a un seul paramètre, qui est le prix initial (avant remise) du produit : appelons ce paramètre prix-initial. Les opérations à réaliser au sein de la fonction sont les suivantes :

  1. Définition de la variable représentant le taux de remise : (define taux 1/3)
  2. Calcul de la remise : (define remise (* prix-initial 1/3))
  3. Application de la remise et affichage du prix net : (- prix-initial remise)

Notre fonction s’écrit donc comme suit :

(define (calcul-prix-net prix-initial)
    (define taux 1/3)
    (define remise (* prix-initial 1/3))
    (- prix-initial remise))

Après avoir entré cette fonction dans le REPL, on doit tester l’exactitude de notre fonction en appliquant la fonction aux trois cas précédents, calculés à la main. Voici les résultats :

> (calcul-prix-net 60)
\(=>\) 40
> (calcul-prix-net 84)
\(=>\) 56
> (calcul-prix-net 117)
\(=>\) 78

Ces résultats sont les mêmes que ceux que nous avions obtenus manuellement. Notre code fait donc les calculs correctement. Comme vous pouvez le voir, nous avons maintenant une seule fonction et on l’utilise pour plusieurs valeurs du paramètre. C’est vraiment ça l’utilité des fonctions, le niveau d’abstraction auquel elles nous élève : c’est déjà impressionnant.

L’expression (calcul-prix-net 60) qui exprime l’utilisation de la fonction calcul-prix-net est appelée appel de la fonction ou application de la fonction à l’argument 60. Notez que lorsqu’on appelle la fonction, on donne des valeurs à ses paramètres, on ne les appelle donc plus paramètres mais arguments.

Le précédent code vous satisfait amplement. Ça a considérablement augmenté votre efficacité à la caisse. Mais, voici que votre boss décide d’appliquer des taux de remise différents en fonction des produits. Par exemple, le produit A a un taux de remise de 1/3 de sa valeur initiale, le produit B, un taux de 1/2 de sa valeur initiale et le produit C a un taux de 1/10 de sa valeur initiale. Est-ce que votre code est adapté à cette situation ? Bien-sûr que non. Et pourquoi donc ? Parce que vous avons défini la variable taux directement dans la fonction et non comme paramètre. Du coup il nous est impossible de contrôler le taux de remise. Il faut donc y remédier. On développe donc cette nouvelle fonction :

(define (calcul-prix-net prix-initial taux)
   (define remise (* prix-initial taux))
   (- prix-initial remise)

Dans cette nouvelle implémentation de la fonction calcul-prix-net, nous avons mis le taux de remise comme paramètre et remplacé tous les 1/3 par taux. Cette fonction est plus puissante que la précédente. Voici quelques exemples d’utilisation :

> (calcul-prix-net 60 1/3)
\(=>\) 40
> (calcul-prix-net 84 1/2)
\(=>\) 42
> (calcul-prix-net 117 1/10)
\(=>\) \(105 \frac{3}{10}\)

Notez que \(105 \frac{3}{10}\) est une représentation du nombre fractionnaire \(105 + \frac{3}{10}\).

Normalement, notre fonction est vraiment bien et nous en sommes très fiers. Mais, nous pouvons encore la rendre moins bavarde. En effet, puisque nous ne réutilisons la variable remise qu’une seule fois, ça ne sert à rien de la définir explicitement. On peut simplement remplacer son expression dans la dernière forme (- prix-initial remise). Ce qui nous donne la fonction suivante :

(define (calcul-prix-net prix-initial taux)
   (- prix-initial (* prix-initial taux))

Cette fonction est exactement équivalente à la précédente mais convenez avec moi qu’elle est plus courte, donc moins bavarde 😉 .

Votre boss vient encore à vous et vous dit qu’il y a, pour certaines catégories de produit, un prix minimal à partir duquel on commence à applique la remise. Si la valeur initiale du produit est plus petite que le prix minimal correspondant à sa catégorie, aucune remise n’est appliquée. Et il vous donne le tableau suivant :

\begin{array}{cc}
\hline
\mathbf{Catégorie} & \mathbf{Prix~minimal~(en~francs)} \\ \hline \hline
A & 10 \\
B & 45 \\
C & 100 \\ \hline
\end{array}

Analysons les fonctionnalités à ajouter à notre fonction. D’abord, il y a un nouveau paramètre à contrôler : la catégorie du produit. Appelons-le categorie. On ne considère pas le prix minimal comme paramètre car il est fixé pour chaque catégorie. On doit maintenant choisir le type de la variable categorie. Nous avons le choix entre les types caractère, symbole et string. Lequel choisir ? Le type caractère nous obligerait à utiliser des arguments tels que #\A ou #\B, ce qui est un peu verbeux, donc on écarte ce choix. Le type string nous ferait utiliser des arguments tels que “A” ou “B” et le type symbole, des arguments tels que ‘A et ‘B. A priori, l’un ou l’autre de ces types pourrait convenir mais nous préférons le type symbole car le prédicat pour vérifier l’identité de deux symboles, eq?, est plus primitif (et plus rapide) que celui pour vérifier l’égalité de deux string, string=?.
Enfin, nous avons, vu tel que le boss a posé le problème, que nous devons utiliser des expressions conditionnelles. Par exemple, Si le produit coûte 7 francs et qu’il est de catégorie A, donc le prix est plus petit que la valeur minimale de la catégorie A, on n’applique pas de remise : le client paye exactement le prix initial du produit. Sinon si le produit coûte 20 francs, donc le prix est plus grand que la valeur minimale de la catégorie A, on applique donc la remise spécifiée : le client reçoit une remise sur le produit.

Voici comment on pourrait implémenter cette fonction :

(define (calcul-prix-net prix-initial categorie taux)
   (define remise (* prix-initial taux))
   (cond [(eq? categorie 'A)
          (if (< prix-initial 10)
              prix-initial
              (- prix-initial remise))]
         [(eq? categorie 'B)
          (if (< prix-initial 45)
              prix-initial
              (- prix-initial remise))]
         [(eq? categorie 'A)
          (if (< prix-initial 100)
              prix-initial
              (- prix-initial remise))]
         [else (error "Catégorie non reconnue")]))

Remarquez que dans cette fonction, nous avons de nouveau défini la variable remise, car elle est utilisée plusieurs fois. L’expression conditionnelle cond a quatre clauses : une clause par catégorie et la dernière clause prenant en compte tous le cas où on passerait en argument, une catégorie inconnue telle que ‘F, ‘X ou ‘Z ou même ‘a, ‘b. Cette dernière clause utilise l’opérateur error pour signaler une erreur. Dans chaque clause, il y a une expression conditionnelle if pour décider si on fait la remise ou pas. Voici quelques exemples d’utilisation de cette fonction :

> (calcul-prix-net 60 ‘A 1/3)       ; remise appliquée
\(=>\) 40
> (calcul-prix-net 7 ‘A 1/2)        ; pas de remise
\(=>\) 7
> (calcul-prix-net 117 ‘G 1/10)
\(=>\) Catégorie non reconnue    ; erreur

Ces exemples parlent d’eux-mêmes donc je ne vais pas vous emmerder avec des explications supplémentaires. Remarquez juste que j’ai ajouté du texte explication à la fin de certaines lignes et chaque texte est précédé d’un point-virgule (;). On appelle ce type de texte un commentaire. Les commentaires permettent d’expliquer certaines parties du code afin que le programmeur initial ou un autre puisse comprendre le code des mois voire des année plus tard. Le REPL les ignore lors de l’évaluation du code. En LISP, les commentaires sont de quatre formes :

  • Le point-virgule (;) : se place en fin d’une ligne de code pour commenter cette ligne.
  • Le double point-virgule (;;) : se place avant une forme pour l’expliquer.
  • Le triple point-virgule (;;;) : se place en début d’un programme entier pour expliquer ce que fait le programme, ses différentes fonctionnalités et des exemples d’utilisation. On place le symbole (;;;) au début de chaque ligne.
  • Les symbole #||# : utilisé en lieu et place du triple point-virgule pour délimiter un commentaire d’un très grand nombre de lignes. Les points de suspension (…) représente le texte.

Nous verrons des exemple d’utilisation de chacune de ces formes au fil de notre évolution.

 

Nous voici donc à la fin de cette seconde partie du cours sur les bases de la programmation Racket. Au chapitre suivant, nous verrons d’autres constructions importantes de Racket et d’autres applications. D’ici-là, revoyez bien tout ce que nous avons appris ici et soyez fiers de ce que vous êtes déjà des racketeurs. 😉

  •  
    21
    Shares
  • 21
  •  
  •  
  •  
  •  

Poster un Commentaire

avatar
  S’abonner  
Notifier de