61 votes

En quoi la boucle read-eval-print de Lisp est-elle différente de celle de Python ?

J'ai rencontré les éléments suivants déclaration de Richard Stallman :

Lorsque vous démarrez un système Lisp, il entre dans une boucle de lecture-évolution-impression. La plupart des autres langages n'ont rien de comparable à read, rien de comparable à eval, et rien de comparable à print. Quelles lacunes flagrantes ! '

J'ai très peu programmé en Lisp, mais j'ai écrit une quantité considérable de code en Python et récemment un peu en Erlang. J'avais l'impression que ces langages offraient également la boucle read-eval-print, mais Stallman n'est pas d'accord (du moins en ce qui concerne Python) :

J'ai parcouru la documentation de Python après qu'on m'ait dit qu'il était fondamentalement similaire à Lisp. Ma conclusion est que ce n'est pas le cas. Lorsque vous lancez Lisp, il fait "read", "eval" et "print", qui sont tous absents de Python.

Y a-t-il vraiment une différence technique fondamentale entre les boucles de lecture-évaluation-impression de Lisp et de Python ? Pouvez-vous donner des exemples de choses que le REPL de Lisp rend faciles et qui sont difficiles à faire en Python ?

57voto

Greg Hewgill Points 356191

Pour appuyer la position de Stallman, Python ne fait pas la même chose que les systèmes Lisp typiques dans les domaines suivants :

  • Le site read en Lisp lit une expression S, qui représente une structure de données arbitraire qui peut être traitée comme des données ou évaluée comme du code. La fonction la plus proche en Python lit une simple chaîne de caractères, que vous devez analyser vous-même si vous voulez qu'elle ait un sens.

  • Le site eval en Lisp peut exécuter n'importe quel code Lisp. Le site eval en Python évalue seulement et a besoin des expressions exec pour exécuter les déclarations. Mais les deux fonctionnent avec le code source Python représenté sous forme de texte, et vous devez passer par un tas d'obstacles pour "évaluer" un AST Python.

  • Le site print en Lisp écrit une expression S exactement de la même manière que la fonction read accepte. print en Python imprime quelque chose de défini par les données que vous essayez d'imprimer, ce qui n'est certainement pas toujours réversible.

La déclaration de Stallman est un peu fallacieuse, car il est clair que Python hace ont des fonctions nommées exactement eval y print mais ils font quelque chose de différent (et inférieur) à ce qu'il attend.

À mon avis, Python hace ont certains aspects similaires à Lisp, et je peux comprendre pourquoi les gens ont pu recommander à Stallman de se pencher sur Python. Cependant, comme Paul Graham soutient dans What Made Lisp Different (Ce qui rend Lisp différent) tout langage de programmation qui inclut toutes les capacités de Lisp, doit également être Lisp.

30voto

user4815162342 Points 27348

L'argument de Stallman est que le fait de ne pas implémenter un "reader" explicite fait que le REPL de Python semble handicapé par rapport à Lisps, car il supprime une étape cruciale du processus REPL. Le lecteur est le composant qui transforme un flux d'entrée textuel dans la mémoire - pensez à quelque chose comme un analyseur XML intégré dans le langage et utilisé à la fois pour le code source et pour la lecture. y pour les données. Ceci est utile non seulement pour écrire des macros (ce qui serait en théorie possible en Python avec la fonction ast ), mais aussi pour le débogage et l'introspection.

Disons que vous êtes intéressé par la façon dont le incf un formulaire spécial est mis en œuvre. Vous pouvez le tester comme suit :

[4]> (macroexpand '(incf a))
(SETQ A (+ A 1)) ;

Mais incf peut faire beaucoup plus que d'incrémenter les valeurs des symboles. Que fait-il exactement lorsqu'on lui demande d'incrémenter une entrée de table de hachage ? Voyons voir :

[2]> (macroexpand '(incf (gethash htable key)))
(LET* ((#:G3069 HTABLE) (#:G3070 KEY) (#:G3071 (+ (GETHASH #:G3069 #:G3070) 1)))
 (SYSTEM::PUTHASH #:G3069 #:G3070 #:G3071)) ;

Nous apprenons ici que incf appelle un système spécifique puthash qui est un détail d'implémentation de ce système Common Lisp. Notez comment l'"imprimante" utilise des fonctionnalités connues du "lecteur", comme l'introduction de symboles anonymes avec la fonction #: et se référant aux mêmes symboles dans la portée de l'expression étendue. Emuler ce type d'inspection en Python serait beaucoup plus verbeux et moins accessible.

En plus des utilisations évidentes au niveau du REPL, les lispers expérimentés utilisent print y read dans le code comme un outil de sérialisation simple et facilement disponible, comparable à XML ou json. Alors que Python possède le str équivalente à la fonction Lisp print il lui manque l'équivalent de read l'équivalent le plus proche étant eval . eval confond bien sûr deux différents concepts, l'analyse syntaxique et l'évaluation, ce qui conduit à des problèmes comme celui-ci y des solutions comme celle-ci et est un sujet récurrent sur les forums Python. Ce ne serait pas un problème en Lisp, précisément parce que le lecteur et l'évaluateur sont proprement séparés.

Enfin, les fonctions avancées de la fonction de lecture permettent au programmeur d'étendre le langage d'une manière que même les macros ne pourraient pas fournir autrement. Un exemple parfait d'une telle possibilité de faire des choses difficiles est le suivant le site infix paquet de Mark Kantrowitz, qui implémente une syntaxe infixe complète sous forme de macro de lecture.

20voto

Rainer Joswig Points 62532

Dans un système basé sur Lisp, on développe généralement le programme pendant son exécution à partir de la boucle REPL (read eval print). Il intègre donc un ensemble d'outils : complétion, éditeur, interpréteur de ligne de commande, débogueur, ... Par défaut, c'est le cas. Tapez une expression avec une erreur - vous êtes dans un autre niveau de REPL avec des commandes de débogage activées. Vous devez en fait faire quelque chose pour vous débarrasser de ce comportement.

Vous pouvez avoir deux significations différentes du concept de REPL :

  • la boucle Read Eval Print comme en Lisp (ou quelques autres langages similaires). Elle lit les programmes et les données, elle les évalue et imprime les données résultantes. Python ne fonctionne pas de cette manière. Le REPL de Lisp vous permet de travailler directement de manière méta-programmée, en écrivant du code qui génère (du code), vérifie les expansions, transforme le code réel, etc. Lisp a read/eval/print comme boucle supérieure. Python a quelque chose comme readtring/evalate/printstring comme boucle supérieure.

  • l'interface de ligne de commande. Un shell interactif. Voir par exemple pour IPython . Comparez cela à la méthode de Common Lisp SLIME .

Le shell de Python en mode par défaut n'est pas vraiment très puissant pour une utilisation interactive :

Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a+2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

Vous obtenez un message d'erreur et c'est tout.

Comparez cela au CLISP REPL :

rjmba:~ joswig$ clisp
  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49 (2010-07-07) <http://clisp.cons.org/>

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010

Type :h and hit Enter for context help.

[1]> (+ a 2)

*** - SYSTEM::READ-EVAL-PRINT: variable A has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of A.
STORE-VALUE    :R2      Input a new value for A.
ABORT          :R3      Abort main loop
Break 1 [2]> 

CLISP utilise le système de condition de Lisp pour s'introduire dans un REPL de débogueur. Il présente quelques redémarrages. Dans le contexte des erreurs, le nouveau REPL fournit des commandes étendues.

Utilisons le :R1 redémarrer :

Break 1 [2]> :r1
Use instead of A> 2
4
[3]> 

Ainsi, vous obtenez une réparation interactive des programmes et des exécutions...

6voto

Vatine Points 8884

Le mode interactif de Python diffère du mode "lire le code à partir d'un fichier" de Python par plusieurs petites choses cruciales, probablement inhérentes à la représentation textuelle du langage. Python n'est pas non plus homoiconique, ce qui me pousse à l'appeler "mode interactif" plutôt que "boucle de lecture-évaluation-impression". Cela mis à part, je dirais que c'est plus une différence de niveau qu'une différence de nature.

Dans un fichier de code Python, vous pouvez facilement insérer des lignes vides :

def foo(n):
  m = n + 1

  return m

Si vous essayez de coller le même code dans l'interpréteur, il considérera que la fonction est "fermée" et se plaindra que vous avez une déclaration de retour nue à la mauvaise indentation. Cela ne se produit pas en Lisp (commun).

En outre, il existe des variables pratiques dans Common Lisp (CL) qui ne sont pas disponibles (du moins pour autant que je sache) dans Python. CL et Python ont tous deux une "valeur de la dernière expression" ( * dans CL, _ en Python), mais CL dispose également de ** (valeur de l'expression avant la dernière) et *** (la valeur de celui qui le précède) et + , ++ y +++ (les expressions elles-mêmes). CL ne fait pas non plus de distinction entre les expressions et les déclarations (par essence, tout est une expression) et tout cela contribue à créer une expérience REPL beaucoup plus riche.

Comme je l'ai dit au début, il s'agit plus d'une différence de niveau que de nature. Mais si l'écart n'avait été qu'un tout petit peu plus grand entre eux, il s'agirait probablement aussi d'une différence de nature.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X