Parenthèses Lisp

Pourquoi Lispers formate-t-il son code comme indiqué dans l’exemple 1 au lieu de l’exemple 2? Pour moi (et je suppose, pour la plupart des personnes venant d’horizons de programmation différents de Lisp), la mise en forme présentée dans l’exemple 2 serait plus facile à lire. Y a-t-il une raison particulière pour laquelle Lispers préfère le style sample 1?

Échantillon 1

(defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1))))) 

Échantillon 2

 (defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1))) ) ) 

Les environnements LISP IDE ont tendance à équilibrer automatiquement les parenthèses et à gérer les indentations en fonction du niveau d’imbrication. L’échantillon 2 n’apporte aucun avantage dans ces situations.

Dans l’inheritance C / FORTRAN / Pascal, vous avez tendance à mettre l’accent sur le séquençage plutôt que sur l’imbrication (les arbres d’parsing de code sont moins profonds et plus larges). La fin de la scope est un événement plus important dans votre code: l’accent a donc été et rest, dans une certaine mesure, plus important.

Pour les utilisateurs expérimentés de Lisp, le niveau d’imbrication est plus important que la recherche de parenthèses fermantes. Mettre des parenthèses fermantes sur leurs propres lignes n’aide pas vraiment les niveaux d’imbrication.

L’idée de base est que les parenthèses sont directement autour de leur contenu.

 (a) 

et pas

 (a ) 

Ce qui suit est le suivant:

 (defun foo (bar) (foo (bar (baz ... ) ... ) ... ) ) 

contre.

 (defun foo (bar) (foo (bar (baz ...) ...) ...)) 

Une des idées de base lors de l’édition de texte Lisp est que vous pouvez sélectionner une liste en cliquant (double-) sur les parenthèses (ou en utilisant un raccourci clavier lorsque le curseur est dans l’expression ou entre parenthèses). Vous pouvez ensuite couper / copier l’expression et la coller dans une autre fonction. L’étape suivante consiste à sélectionner l’autre fonction et à ré-indenter la fonction. Terminé. Il n’est pas nécessaire de supprimer ou d’introduire de nouvelles lignes pour la fermeture des parenthèses. Il suffit de coller et de re-indenter. Il s’intègre simplement. Sinon, vous perdriez du temps à formater le code, ou vous auriez à reformater le code avec un outil d’éditeur (afin que les parenthèses fermantes se trouvent sur leurs propres lignes. La plupart du temps cela créerait un travail supplémentaire). et entrave le déplacement du code.

Il y a une occasion où les Lispers expérimentés écrivent parfois des parenthèses fermantes sur leur propre ligne:

 (defvar *persons* (list (make-person "fred") (make-person "jane") (make-person "susan") )) 

Ici, cela indique que de nouvelles personnes peuvent être ajoutées. Placez le curseur directement avant la deuxième parenthèse fermante sur la dernière ligne, appuyez sur co (ligne ouverte), ajoutez la clause et indiquez les parenthèses à nouveau. Cela évite le «problème» de trouver les parenthèses à droite et appuie ensuite sur la touche retour, lorsque toutes les parenthèses sont fermées sur une ligne.

Parce qu’il n’y a aucun besoin d’aligner la fermeture des parens. Il n’ajoute rien sémantiquement. Pour savoir combien de fichiers se fermer, la plupart des lispers utilisent un bon éditeur, tel qu’Emacs, qui associe parens à la fermeture de parens et rend la tâche sortingviale.

En Python, il n’y a pas de parens de fermeture ni de mots clés de end , et les Pythonistas fonctionnent bien.

Après un certain temps avec Lisp, vous ne remarquerez plus les parenthèses, donc votre exemple deux semble avoir un tas d’espaces inutiles à la fin. Plus précisément, la plupart des lispers utilisent un éditeur connaissant les parenthèses et se charge de fermer les formulaires correctement, de sorte que vous, en tant que développeur, n’ayez pas besoin de faire correspondre les parenthèses d’ouverture et de fermeture comme par exemple C.

Quant à savoir si la dernière forme devrait être

 (* n (factorial (- n 1))) 

ou

 (* n (factorial (- n 1))) 

cela se résume surtout aux préférences personnelles et au contenu du code (dans ce cas, je préférerais le premier simplement parce qu’il y a si peu de choses dans le code).

Outre le fait que la plupart des IDE Lisp utilisent la correspondance paren, la quantité d’espace que vous utiliseriez pour écrire un programme raisonnable serait ridicule! Vous obtiendrez le canal carpien de tous les défilement. Un programme de 1 000 lignes serait proche d’un million de lignes de code si vous placez toutes les parenthèses fermantes sur leur propre ligne. Il peut sembler plus joli et plus facile à lire dans un petit programme, mais cette idée ne serait pas du tout bien adaptée.

Pourquoi les programmeurs C écrivent-ils des choses comme:

 ((a && b) || (c && d)) 

au lieu de

 ((a && b) || (c && d ) ) 

N ° 2 ne serait-il pas plus facile à lire? 🙂

Sérieusement, cependant, le style de fermeture groupé fonctionne bien pour d’autres langues.

 #include  #include  int main(void) { int i, j, k; for (i = 0; i < 1000; i++) { for (j = 0; j < 1000; j++) { for (k = 0; k < 1000; k++) { if (i * i + j * j == k * k) { printf("%d, %d, %d\n", i, j, k); } } } } return 0; } 

Après un certain temps, vous ne "voyez" plus les accolades, juste la structure donnée par l'indentation. if / else ressemble à ceci:

 if (cond) { stmt; stmt; } else { stmt; } 

commutateur:

 switch (expr) { case '3': stmt; break; case '4': { stmt; break; } } 

structs:

 struct foo { int x; struct bar { int y; }; union u { int a, b; char c; }; }; 

J’ai expérimenté le même dilemme en PHP en utilisant le framework CakePHP et ses nombreuses structures de array() nestedes array() , parfois même de plus de 5 couches. Au bout d’un moment, je me suis rendu compte que le fait de ne pas oublier l’OCD à propos de la parenthèse finale ne faisait que perdre mon précieux temps de développement et me détournait du but final. L’indentation devait être l’aspect le plus utile du formatage.

Sauver tout l’espace semble être la raison principale. Si c’est presque aussi lisible (en particulier une fois que vous avez l’habitude de la syntaxe), pourquoi utiliser les 3 lignes supplémentaires?

Raison première et évidente: 4 LOC dans le premier cas contre 7 LOC dans le second.

Tout éditeur de texte connaissant la syntaxe LISP mettra en évidence les parenthèses appariées / incompatibles, ce n’est donc pas le problème.

Parce que les programmeurs Lisp regardent l’indentation et la “forme”, pas les crochets.

Vous pourriez envisager des variantes de Lisp sans le (). En Genyris, cela ressemble à ceci:

 def factorial (n) if (< n 2) 1 * n factorial (- n 1) 

On dirait que ce n’est pas une vraie question, juste un jugement sur Lisp, mais la réponse est parfaitement évidente (mais pas, apparemment, pour les non-Lispers!): Les parenthèses dans Lisp ne sont nullement équivalentes aux accolades en langage C – plutôt, ils sont précisément équivalents aux parenthèses dans les langages de type C. Lispers n’écrit généralement pas

 (foo bar baz ) 

pour exactement la même raison que les programmeurs C n’écrivent généralement pas

 foo(bar, baz ); 

La raison

 (if foo (bar) (baz)) 

semble différent de

 if (foo) { bar(); } else { baz(); } 

a plus à voir avec les if que les parenthèses!

J’ai une explication concernant les programmeurs C qui ont un problème avec Lispers qui met toutes les parenthèses fermantes à la fin. La pratique me semble que la signification des parenthèses pourrait en être la raison. Cette explication chevauche et élargit certaines autres réponses.

Pour un programmeur C (ou un autre langage utilisant {accolades} comme C), Lisp semble utiliser # | comments | # au lieu de / * commentaires * /. Et on dirait que les parenthèses Lisp () sont à la place des accolades C}.

Mais en réalité, les parenthèses Lisp signifient très proche de la même chose qu’en C (et des langages similaires utilisant des parenthèses). Considérer

 defun f () nil 

Ceci définit une fonction f qui ne fait absolument rien, nulle. Et je tape cette ligne exactement comme dans le Lisp Listener, et elle répond simplement “F”. En d’autres termes, il l’accepte, sans début ni fin de parenthèse autour du tout.

Ce que je pense se produit lorsque vous tapez cela dans le Listener, le Listener passe ce que vous avez tapé à Eval. Le transmettre à Eval peut être représenté par:

 Eval( Listener() ) 

Et l’auditeur accepte mon entrée et retourne ce que j’ai tapé. Et vous considéreriez “Listener ()” remplacé dans l’expression pour qu’il devienne

 Eval (defun f () nil) 

REPL (lecture, évaluation, impression, boucle) pourrait être représenté conceptuellement comme une récursivité

 /* REPL expressed in C */ Loop() { Print ( Eval ( Listen () ) ); Loop(); } 

Compris dans l’exemple, Listen (), Eval () et Print () sont définis ailleurs ou sont intégrés au langage.

L’auditeur me permet de taper

 setf a 5 

et me laisse aussi taper

 (setf a 5) 

mais se plaint quand je tape

 ((setf a 5)) 

Si les parenthèses étaient équivalentes aux accolades en langage C, l’auditeur l’accepterait. Dans mes compilateurs C \ C ++, je peux taper

 void a_fun(void) {{ }} 

sans plainte. Je peux même taper

 void a_fun(void) {{{ }}} 

sans plainte du compilateur C / C ++.

Mais si j’ose taper

 void a_fun((void)) { } 

mon compilateur C / C ++ se plaint – tout comme le programme Lisp se plaint!

Pour un programmeur en C écrivant des appels de fonctions nesteds, il semble plus naturel d’écrire

 fun_d( fun_c( fun_b( fun_a()))); 

que d’écrire

 fun_d( fun_c( fun_b( fun_a() ) ) ); 

En C, les accolades délimitent un bloc de code. Et un bloc de code fait partie d’une définition de fonction. Les parenthèses Lisp ne délimitent pas les blocs. Ils sont un peu comme les parenthèses de C. En C, les parenthèses délimitent une liste; peut-être une liste de parameters passés à une fonction.

Dans Lisp, il semble que les parenthèses ouvrantes “(” signifie que nous allons lancer une nouvelle liste, sur laquelle le côté CAR d’une construction CONS pointera. Ensuite, nous listons les éléments à contenir ou éventuellement pointés par le côté CAR du CONS, avec le côté CDR pointant vers nil (pour la fin de la liste) ou le prochain CONS. La parenthèse fermante “)” signifie terminer la liste par un zéro, et revenir en arrière pour continuer le niveau de liste précédent. Donc, le langage C ne fait pas de CONS. Il y a donc une petite différence entre les parenthèses de C et celles de Lisp. Même si, semble très proche, peut-être même pratiquement la même chose.

Certains ont écrit que Lisp ressemble plus à C si vous déplacez la fonction en dehors des parenthèses. Par exemple,

 (setf a 5) 

devient

 setf(a 5) 

Mais en réalité, Eval exige que le premier élément d’une liste soit la fonction que Eval doit appeler. C’est plus comme passer une fonction C un pointeur sur une fonction. Donc, ce que c’est vraiment c’est

 eval(setf a 5) 

Et cela ressemble plus à C. Vous pouvez même le taper dans l’auditeur comme cela sans se plaindre de l’auditeur ou de l’eval. Vous dites simplement que eval devrait appeler eval qui devrait alors appeler setf. Le rest de la liste sont des parameters à définir.

En tout cas, c’est juste ce que je pense voir.

C’est juste qu’Eval a besoin de savoir quel processeur appeler.

Et je pense que cette explication permet de comprendre pourquoi les programmeurs C pensent initialement que Lispers doit aligner les parenthèses fermantes sous les parenthèses d’ouverture qu’elles ferment. Le programmeur C pense à tort que les parenthèses de Lisp correspondent aux accolades de C. Ils ne le font pas. Les parenthèses de Lisp correspondent aux parenthèses de C.