La semaine passé, j'ai brèvement mentionné l'homoiconicité présente dans Lisp dans le contexte d'intelligence artificielle. Pourtant, c'est un des concepts les plus intéressants de ce langage et je trouve qu'il serait dommage de ne pas l'explorer d'avantage.
Pour vous rappeller les bases de lisp, une fonction est construite ainsi :
(+ 1 2)
Le premier argument (+) représente une fonction et les arguments qui suivent sont passés à cette dernière.
Une liste est aussi construite de la même façon :
(list 'banane pomme orange)
Étant donné qu'un fonction partage la même syntaxe qu'une liste, elle peut être placée à l'intérieur de celle ci ainsi :
(list '+ 2 3)
Il est fréquent d'appliquer des fonctions à des listes, que ce soit pour multiplier, additionner ou même rendre majuscules tous les éléments d'une liste, et puisque les fonctions peuvent être traitées comme des objets dans ces listes, elles peuvent être rédéfinies après la compilation du programme.
Un cas commun serait de filtrer une liste pour obtenir tous les éléments qui sont pairs. Nous pouvons faire la même chose avec des fonctions, selon leur variable de retour.
Créons quelques simples fonctions d'addition et multiplication :
(defun plus-deux (x)
(+ x 2))
(defun plus-cinq (x)
(+ x 5))
(defun fois-dix (x)
(* x 10))
(defun fois-cinq (x)
(* x 5))
Nous pouvons ensuite placer ces listes à l'intérieur d'une fonction :
(defvar liste-de-fonctions '(plus-deux plus-cinq fois-cinq fois-dix))
Nous filtrons ensuite chaque fonction qui ne retournent pas un nombre pair :
(defun filtrer-fonctions-paires (var)
(remove-if-not (lambda (func) (evenp (funcall func var) 5)) function-list))
Nous créons ici une fonction nommée filtrer-fonctions-paires qui prend une variable et qui exécute la méthode remove-if-not, une autre fonction qui elle prend en paramètre une méthode et une liste, comparant si chaque élément correspond à la méthode fournise. Nous appellons ensuite une fonction lambda qui prend en variable chaque fonction de la liste et l'éxécute à l'aide de funcall, passant à cette fonction la variable initiale et vérifiant ensuite si le résultat est pair, grâce à evenp.
Nous pouvons maintenant invoquer notre fonction filtrer-fonctions-paires ainsi et et lui donner un argument :
(filtrer-fonctions-paires 5)
Nous pouvons vérifier si tout s'est bien passé en afichant la liste avant et le résultat de la fonction :
(print liste-de-fonctions)
(print (filtrer-fonctions-paires 5))
Le code sera mis au complet avec de légères modifications pour que vous puissiez l'essayer par vous-même sur emacs à l'aide d'eval-buffer.
Malgré la simplicité de l'exercice, le code en lisp devient rapidement mélangeant avec plusieurs fonctions imbriquées les unes dans les autres. C'est malheureusement un des défauts de ce langage; plus le code devient complexe, plus il devient difficile à comprendre et à maintenir.
Au delà de cette limitation, l'homoiconicité dans lisp nous ouvre encore une fois la porte à des possibilités infinies. Nous n'avons pas à nous limiter à de l'addition et multiplication de nombre, cd concept pourrait être utiliser pour générer des fonctions et ensuite filtrer ces fontions afin d'optimiser une application plus large, que ce soit en comparant le temps d'éxécution, où la précision de certaines fonctions, dans le cas d'un programme qui fait de la reconnaissance d'objets ou de voix.
Ce concept m'a mené à repenser la programmation et comment le code peut être utilisé en dehors de notre zone de familiarité avec des langages plus modernes. J'espère que ce sujet à piqué votre curiosité et je vous invite à faire des recherches sur ce sujet complexe puisque mon explication ne touche que la pointe du iceberg qu'est l'homoiconicité.
Références
"Code vs Data (Metaprogramming) - Computerphile", Robert Smith, Mai 18, 2018, Url : [https://www.youtube.com/watch?v=dw-y3vNDRWk&t=139s]
Code complet (elisp)
(require 'cl-lib)
(defun plus-deux (x)
(+ x 2))
(defun plus-cinq (x)
(+ x 5))
(defun fois-dix (x)
(* x 10))
(defun fois-cinq (x)
(* x 5))
(defvar liste-de-fonctions '(plus-deux plus-cinq fois-cinq fois-dix))
(defun filtrer-fonctions-paires (var)
(cl-remove-if-not (lambda (func) (cl-evenp (funcall func var))) liste-de-fonctions))
(message "List of functions: %S" liste-de-fonctions)
(message "Filtered functions: %S" (filtrer-fonctions-paires 5))
Commentaires1
Les fonctions lambda comme…
Les fonctions lambda comme base du langage... en 1958!