Xavier Nayrac

Rubyiste accro au TDD, serial blogger, apprenti data scientist, heureux utilisateur de Vim, accordéoniste.
Si vous vous sentez particulièrement généreux, suivez moi sur Twitter.

Racket: première approche de lambda

| Comments

Niveau : facile

Dans un article précédent, j’ai écrit une fonction Racket pour calculer les diviseurs d’un nombre n:

1
2
3
4
5
6
7
8
9
#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:

number.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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:

1
2
3
4
; 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:

1
2
3
; 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:

1
2
(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:

number.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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:

1
2
3
4
; 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.

Articles connexes

Commentaires