lundi 22 août 2016

21-FONCTIONS et FERMETURES: (CLOSURES:)

>>>>
Fonctions et fermetures:

Programmes pour construire des fonctions:

(defun make-add (n)
  #'(lambda (x) (+ x n)))
MAKE-ADD       rend une fonction qui ajoute n à sa variable x
    il ne reste qu'à donner un nom à cette fonction par:

(setf add3 (make-add 3))
#
   définition de ADD3 qui prend comme valeur une fonction
 à l'aide de la fermeture LAMBDA (de ADD)
La fonction est appelée par:

(funcall add3 15)
18 ajoute 3 à l'argument 15

Il peut être intéressant de changer le nombre "fixe" à ajouter. Voici donc une variante de MAKE-ADD:

(defun make-add-b (n)
  #'(lambda (x &optional chgt) ;chgt est une expression dont la valeur de vérité importe pour la suite
      (if chgt   ;si vrai...
  (setq n x) ;...n prend la valeur de x, sinon...
  (+ x n)))) ;...n est ajouté à x

(defparameter add-b nil)
ADD-B      la variable ADD-B est initialisée à NIL

(setq add-b (make-add-b 1))
# ADD-B prend pour valeur une fonction qui ajoute 1 (arbitrairement)

(funcall add-b 3)
4 4 est bien le résultat de 3+1

(funcall add-b 5 t)
5 l'option T permet de changer la valeur à ajouter: 5 ici

(funcall add-b 3)
8 vérification: 3+5

Fonction linéaire mult5: x -> 5x

(defun mult (n)
  #'(lambda (x) (* x n)))
MULT       rend une fonction qui multiplie par n la variable x

(setf mult5 (mult 5))
#
   définition de mult5 qui prend comme valeur une fonction
 à l'aide de la fermeture LAMBDA (de MULT)

(funcall mult5 6)
30

Comme nous l'avons fait pour ADD, on peut vouloir changer le facteur de multiplication:

(defun make-mult-a (n)
  #'(lambda (x &optional chgt) ;chgt est une expression dont la valeur de vérité importe pour la suite
      (if chgt   ;si vrai...
  (setq n x) ;...n prend la valeur de x, sinon...
  (* x n)))) ;...x est multiplié par n

(setq mult-a (make-mult-a 1))
# MULT-A prend pour valeur une fonction qui multiplie par 1 (arbitrairement)

(funcall mult-a 3)
3 rend le résultat de 3x1

(funcall mult-a 2 t)
2 l'option T permet de changer la valeur du facteur (1 devient 2)

(funcall mult-a 3)
6 vérification: 3x2


Et maintenant, fabriquons des fonctions affines (f(x)=ax+b):

(defun affine (a b)
  #'(lambda (x) (+ b (* a x))))
AFFINE       rend une fonction affine x->ax+b

(setf aff2-3 (affine 2 3))
#
   définition de aff2-3: x->2x+3

(funcall aff2-3 3)
9 pour x=3 la fonction vaut 9

Je vous laisse définir une fonction MAKE-AFFINE qui construit une fonction affine de coefficients a et b pouvant changer ces coefficients à l'aide d'un argument optionnel vrai (T).

(mapcar (affine 2 3) '(-3 -2 -1 0 1 2 3))
(-3 -1 1 3 5 7 9)      pour une série de nombres

(mapcar #'cons lst (mapcar (affine 2 3) '(-3 -2 -1 0 1 2 3)))
((-3 . -3) (-2 . -1) (-1 . 1) (0 . 3) (1 . 5) (2 . 7) (3 . 9))
              pour avoir les couples de la forme (abscisse . ordonnée)

La fonction suivante permet de rendre la fonction composée de plusieurs fonctions:

(defun fonction-composée (&rest fns) ;fns est une suite de fonctions passées en argument
  (destructuring-bind (fn1 . rest) (reverse fns)    ;fns est retournée et destructurée sous la forme (fn1 . rest)
    #'(lambda (&rest args)           ;fermeture
(reduce #'(lambda (v f) (funcall f v)) ;appel aux fonctions suivantes dans
rest           ;la suite passée à lambda (2ème)
:initial-value (apply fn1 args))))) ;valeur initiale passée à reduce
FONCTION-COMPOSÉE rend la fonction composée de toutes les fonctions passées en argument

Exemples de composition:

(mapcar (fonction-composée #'list #'round #'sqrt)
'(4 9 16 20 25))
((2) (3) (4) (4) (5))

On se propose de calculer la racine-carrée d'un nombre positif au dixième près.
Pour cela on multiplie le nombre par 100, puis on prend l'arrondi de sa racine-carrée.
On divise le résultat par 10 et on convertit en flottant pour éviter les fractions.

D'abord, on va définir quelques fonctions conformément à ce qui précède:

(setf mult100 (mult 100))
#
   définition d'une fonction qui multiplie par 100
 mult a déjà été définie

(defun div (n)
  #'(lambda (x) (/ x n)))
DIV       rend une fonction qui divise par n

(setf div10 (div 10))
#
   définition d'une fonction qui divise par 10

(setf sqrt-au-10ème (fonction-composée #'float div10 #'round #'sqrt mult100))
#
   rend une fonction-composée qui calcule la racine-carrée au 1/10ème près

(defun racine-carrée-au-1/10 (n)
  "rend la racine-carrée d'un nombre positif au dixième près."
  (if (>= n 0)
      (funcall sqrt-au-10ème n)
      (error "Le nombre en argument doit être positif.")))
RACINE-CARRÉE-AU-1/10   définition de la fonction rendant la racine-carrée au 1/10ème près

(racine-carrée-au-1/10 21)
4.6       la racine-carrée de 21 est 4,6 au 1/10ème près

Construction d'un prédicat qui est la réunion de un ou plusieurs prédicats:
La fonction suivante est souvent nommée DISJOIN:

(defun union-pred (pred &rest preds)        ;un prédicat et le reste
  (if (null preds)        ;si le reste est vide...
      pred        ;...on prend le premier qui est le dernier
      (let ((réunion (apply #'union-pred preds)))   ;la variable réunion contient le prédicat suivant (récursion)
#'(lambda (&rest args)  ;fermeture appliquant...
    (or (apply pred args) (apply réunion args)))))) ;...la réunion des prédicats par cumul sur la récursion
UNION-PRED     rend un nouveau prédicat qui rend T si l'un des prédicats constituants rend T

(setf mon-pred1 (union-pred #'integerp #'symbolp))
#
   définit un nouveau prédicat mon-pred1 (qui n'est donc plus anonyme)

(mapcar mon-pred1 '(a "b" 3 5.4))
(T NIL T NIL)  a est bien un symbole même si ce n'est pas un entier
         "b" n'est ni un entier ni un symbole
 3 est un entier même si ce n'est pas un symbole
 5.4 n'est ni un entier ni un symbole

Construction d'un prédicat qui est l'intersection de un ou plusieurs prédicats:
La fonction suivante est souvent nommée CONJOIN:

(defun inter-pred (pred &rest preds)  ;un prédicat et le reste
  (if (null preds)    ;si le reste est vide...
      pred    ;...on prend le premier qui est le dernier
      (let ((inter (apply #'inter-pred preds))) ;la variable inter contient le prédicat suivant (récursion)
#'(lambda (&rest args) ;fermeture appliquant...
    (and (apply pred args) (apply inter args)))))) ;...une intersection des prédicats par cumul sur la récursion
INTER-PRED     rend un prédicat qui rend T si tous les prédicats constituants rendent T

(mapcar (inter-pred #'integerp #'oddp) '(a "b" 3 5.5))
(NIL NIL T NIL)           seul 3 est un entier impair
              ici, le prédicat utilisé par MAPCAR est laissé anonyme

Voici deux fonctions (souvent nommées CURRY: et RCURRY:) qui permettent
"d'adapter" une fonction à un contexte. Elles rendent une autre fonction
(ou un prédicat) adaptée à gauche ou à droite comme je vais l'expliquer plus loin:

(defun adaptation-gauche (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args args2))))
ADAPTATION-GAUCHE

(defun adaptation-droite (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args2 args))))
ADAPTATION-DROITE

Ces deux fonctions peuvent rendre la même fonction si la fonction
passée en argument a un caractère commutatif.
Si on passe en argument la fonction #'+ qui est commutative, on obtiendra la même
fonction que celle obtenue plus haut avec MAKE-ADD, et à gauche comme à droite:

(funcall (adaptation-gauche #'+ 3) 15)
18

(funcall (adaptation-droite #'+ 3) 15)
18

De plus, comme la fonction #'+ admet de nombreux arguments, on peut écrire par exemple:

(funcall (adaptation-gauche #'+ 3 4) 15 8)
30

Par contre, si on passe en argument la fonction #'- qui n'est pas commutative,
on obtient un résultat à gauche différent du résultat à droite:

(funcall (adaptation-gauche #'- 3) 15)
-12    résultat de 3-15

(funcall (adaptation-droite #'- 3) 15)
12    résultat de 15-3

On peut, également, transformer un prédicat avec ces fonctions d'adaptation:

(funcall (adaptation-gauche #'>= 3) 2.9)
T la question posée par le prédicat est: "est-ce que l'argument est plus petit ou égal à 3?".
donc T, ici.

(funcall (adaptation-gauche #'>= 3) 3)
T même question pour l'argument à la limite.

(funcall (adaptation-gauche #'>= 3) 3.1)
NIL même question pour un argument plus grand que 3 (donc qui n'est pas plus petit ou égal à 3).

Remplaçons dans ces 3 cas précédents "gauche" par "droite":

(funcall (adaptation-droite #'>= 3) 2.9)
NIL la question est, cette fois: "est-ce que l'argument est plus grand ou égal à 3".
donc NIL, ici.

(funcall (adaptation-droite #'>= 3) 3)
T même question à la limite

(funcall (adaptation-droite #'>= 3) 3.1)
T même question pour un argument plus grand que 3

Quelle est la meilleure disposition pour #'>=: adaptation-gauche ou adaptation-droite?
Cela se discute et, bien sûr, il faut savoir ce que l'on veut et ce que l'on fait.
J'ai ma préférence pour adaptation-droite pour cette fonction #'>=.

Je vous laisse réfléchir au cas de la fonction #'<=.
Là aussi, j'ai ma préférence pour adaptation-droite, car c'est la situation de l'argument
qui nous intéresse. Mais, je le répète: cela se discute!

On peut se poser la question de savoir si un nombre se trouve dans un intervalle
(disons fermé) [min max]. Donc s'il est supérieur ou égal à min et inférieur ou
égal max.

(defun intervallep (min max)
  "rend un prédicat qui vérifie si le nombre qu'on lui passe en argument est dans l'intervalle [min max]."
  (inter-pred (adaptation-droite #'>= min) (adaptation-droite #'<= max)))
INTERVALLEP

(mapcar (intervallep -1 2) '(-2 -1 -0.5 1 2 2.5))
(NIL T T T T NIL)     -2 et 2.5 ne sont pas dans l'intervalle [-1 2]

(mapcar #'cons lst (mapcar (intervallep -1 2) '(-3 -2 -1 0 1 2 3)))
((-3) (-2) (-1 . T) (0 . T) (1 . T) (2 . T) (3))
        sous cette forme on voit bien lesz valeurs qui sont dans l'intervalle: -1, 0, 1 et 2.
  rappelons que (-3) c'est aussi (-3 . NIL) écriture réduite automatiquement en lisp.

La fonction suivante est aussi nommée ALWAYS:

(defun toujours (x) #'(lambda (&rest args) x))
TOUJOURS rend une fonction constante dont la valeur vaut celle passée en argument

(setf const (always 2.7))
#

(funcall const)
2.7

(funcall const 5)
2.7

(funcall const 5 6)
2.7       quelque soit le nombre d'arguments passés à la fonction const
      elle rend 2.7

Portée lexicale:    une variable locale est toujours à peu près de portée lexicale.
Portée dynamique:   une variable globale est toujours une variable spéciale de portée dynamique.

(let ((x 10))
  (defun foo (y)
    (+ x y))) ;x se réfère à la variable définie dans ce LET
FOO    

(let ((x 20))
  (foo 2))
12   la fonction foo rend le calcul de 10+2 et non 20+2
  la portée est lexicale

(let ((x 10))
  (defun foo (y)
    (declare (special x))
    (+ x y)))       ;x est déclarée comme spéciale dans la définition de foo
FOO

(let ((x 20))
  (declare (special x)) ;x doit être déclarée comme spéciale pour utiliser la fonction foo
  (foo 2))
22   la fonction foo rend, cette fois le calcul de 20+2 et non 10+2
si x n'est pas déclarée comme spéciale, il y a une erreur
la portée est dynamique

(setf x 30)
30 toute variable initialisée avec setf est implicitement spéciale

(foo 2)
32 vérification

(defparameter x 40)
X      on peut éviter la déclaration précédente en utilisant DEFPARAMETER

(foo 2)
42 defparameter initialise des variables spéciales (ici 40)

*print-base* est une variable globale.
On utilise sa portée dynamique pour changer, temporairement, sa valeur:

(let ((*print-base* 16))
  (princ 15))
F princ affiche la valeur de 15 dans le système hexadécimal
15 puis la valeur de l'expression est rendue dans le système décimal,
après la sortie du LET.

Le prochain chapitre est en préparation: on approfondit SLIME.