Racket: première approche de lambda
Dans un article précédent, j’ai écrit une fonction Racket pour calculer les diviseurs d’un nombre n:
#lang racket
(define (divisors n)
; Is i a divisor of n?
(define (divisor? i)
(= 0 (remainder n i)))
(filter divisor? (range 1 (+ n 1))))
(provide divisors)
Puis on a vu comment faire des tests unitaires. Il est temps maintenant de faire un peu de refactoring.
Tout d’abord, le plus simple, on va extraire une fonction qui calcule un range de 1 à n inclus:
#lang racket
; divisors : integer -> list of integers
; Get divisors of a number n.
(define (divisors n)
; Is i a divisor of n?
(define (divisor? i)
(= 0 (remainder n i)))
(filter divisor? (range-inclusive n)))
; range-inclusive : integer -> list of integers
; Build a list from 1 to n inclusive.
(define (range-inclusive n)
(range 1 (+ n 1)))
(provide divisors)
Vous noterez au passage que j’ai commencé à documenter mes fonctions en
spécifiant les types de données en entrée et en sortie. Vous remarquez aussi
que range-inclusive
ne fait pas partie de l’API du module:
(provide divisors)
.
Maintenant il nous faut extraire la fonction qui regarde si un nombre i est un diviseur de n:
; divisor-of? : integer integer -> boolean
; Tells if i is a divisor of n.
(define (divisor-of? n i)
(= 0 (remainder n i)))
Le nom de la fonction a changé au passage pour divisor-of?
. Mais surtout
nous avons du inclure n dans les arguments de la fonction.
On doit maintenant insérer cette fonction dans le code de la fonction
principale divisors
. Voici une première tentative un peu naive:
; Attention, ce code ne fonctionne pas.
(define (divisors n)
(filter (divisor-of? n i) (range-inclusive n)))
Évidemment ça ne marche pas, puisque Racket ne connait pas i, mais
ça nous donne une orientation. Pour que Racket sache ce que nous voulons
placer dans i, à savoir l’élément en cours de traitement par la fonction
filter
, on va passer par une fonction anonyme:
(define (divisors n)
(filter (lambda (i) (divisor-of? n i)) (range-inclusive n)))
Une fonction anonyme (lambda) prend un argument (ou plusieurs) et une
expression. À chaque itération, filter
passe un élément tiré de
(range-inclusive n)
à la fonction anonyme (lambda (i) (divisors-of? n i))
.
Voilà donc notre module, après refactoring:
#lang racket
; divisors : integer -> list of integers
; Get divisors of a number n.
(define (divisors n)
(filter (lambda (i) (divisor-of? n i)) (range-inclusive n)))
; divisor-of? : integer integer -> boolean
; Tells if i is a divisor of n.
(define (divisor-of? n i)
(= 0 (remainder n i)))
; range-inclusive : integer -> list of integers
; Build a list from 1 to n inclusive.
(define (range-inclusive n)
(range 1 (+ n 1)))
(provide divisors)
On aurait aussi pu écrire ce qui suit, à la place des trois fonctions ci-dessus:
; divisors : integer -> list of integers
; Get divisors of a number n.
(define (divisors n)
(filter (lambda (i) (= 0 (remainder n i))) (range 1 (+ n 1))))
Ça fait bien sûr beaucoup moins de code… Peut-être est-ce parceque je ne suis
pas encore habitué à Racket, mais je trouve aussi cela bien moins lisible.
Si on doit réutiliser les fonctions divisor-of?
et range-inclusive
, il
n’y a pas de question à se poser. Sinon…? Si vous connaissez bien
Racket/Scheme/Lisp laissez donc un commentaire pour me dire quelle version
est la plus idiomatique de ce type de langages.
À demain.