web-dev-qa-db-fra.com

Schéma vs LISP commun: Quelles caractéristiques ont fait la différence dans votre projet?

Il n'y a pas de pénurie de vagues questions "Scheme vs Common LISP" à la fois sur StackOverflow et sur ce site, donc je veux rendre celui-ci plus ciblé. La question s'adresse aux personnes qui ont codé dans les deux langues:

Lors du codage dans Scheme, quels éléments spécifiques de votre expérience de codage LISP commun avez-vous le plus manqué? Ou, inversement, lors du codage dans LISP commun, qu'avez-vous manqué du codage dans Scheme?

Je ne parle pas nécessairement uniquement des fonctionnalités linguistiques. Ce qui suit sont toutes des choses valables à manquer, en ce qui concerne la question:

  • Bibliothèques spécifiques.
  • Caractéristiques spécifiques des environnements de développement comme SLIME, DrRacket, etc.
  • Fonctionnalités d'implémentations particulières, comme la capacité de Gambit à écrire des blocs de code C directement dans votre source Scheme.
  • Et bien sûr, les fonctionnalités linguistiques.

Exemples de réponses que j'espère:

  • "J'essayais d'implémenter X dans Common LISP, et si j'avais eu les continuations de première classe de Scheme, j'aurais totalement fait Y, mais à la place, j'ai dû faire Z, ce qui était plus pénible."
  • "Le scriptage du processus de construction dans mon projet Scheme est devenu de plus en plus pénible à mesure que mon arbre source grandissait et que je me connectais de plus en plus de bibliothèques C. Pour mon prochain projet, je suis revenu à Common LISP."
  • "J'ai une grande base de code C++ existante, et pour moi, pouvoir incorporer des appels C++ directement dans mon code Gambit Scheme valait totalement toutes les lacunes que Scheme pourrait avoir par rapport à Common LISP, y compris même le manque de prise en charge SWIG."

Donc, j'espère des histoires de guerre, plutôt que des sentiments généraux comme "Le schéma est un langage plus simple", etc.

159
SuperElectric

Mon diplôme de premier cycle était en sciences cognitives et en intelligence artificielle. De là, j'ai eu une introduction d'un cours au LISP. Je pensais que le langage était intéressant (comme dans "élégant") mais je n'y pensais pas vraiment jusqu'à ce que je tombe sur la dixième règle de Greenspun beaucoup plus tard:

Tout programme C ou Fortran suffisamment compliqué contient une implémentation ad hoc, spécifiée de manière informelle, remplie de bogues et lente de la moitié de LISP commun.

Le point de Greenspun était (en partie) que de nombreux programmes complexes ont des interprètes intégrés. Plutôt que de construire un interprète dans une langue, il a suggéré qu'il serait plus logique d'utiliser une langue comme LISP qui a déjà un interprète (ou un compilateur) intégré.

À l'époque, je travaillais sur une application assez grande qui effectuait des calculs définis par l'utilisateur à l'aide d'un interpréteur personnalisé pour une langue personnalisée. J'ai décidé d'essayer de réécrire son noyau dans LISP comme une expérience à grande échelle.

Cela a pris environ six semaines. Le code d'origine était d'environ 100 000 lignes de Delphi (une variante Pascal). Dans LISP, ce nombre a été réduit à environ 10 000 lignes. Encore plus surprenant, cependant, le fait que le moteur LISP était 3-6 fois plus rapide. Et gardez à l'esprit que c'était le travail d'un néophyte LISP! Toute cette expérience m'a ouvert les yeux; pour la première fois, j'ai vu la possibilité de combiner performance et expressivité dans une seule langue.

Quelque temps plus tard, lorsque j'ai commencé à travailler sur un projet Web, j'ai auditionné un certain nombre de langues. J'ai inclus LISP et Scheme dans le mix. Au final, j'ai choisi une implémentation de Scheme -- Chez Scheme . Je suis très content des résultats.

Le projet basé sur le Web est un outil performant "moteur de sélection" . Nous utilisons Scheme de différentes manières, du traitement des données à l'interrogation des données en passant par la génération de pages. Dans de nombreux endroits, nous avons commencé avec une langue différente, mais nous avons fini par migrer vers Scheme pour des raisons que je décrirai brièvement ci-dessous.

Je peux maintenant répondre à votre question (au moins en partie).

Au cours de l'audition, nous avons examiné une variété d'implémentations LISP et Scheme. Du côté LISP, nous avons examiné (je crois) Allegro CL, CMUCL, SBCL et LispWorks. Côté Scheme, nous avons examiné (je crois) Bigloo, Chicken, Chez, Gambit. (La sélection de la langue a eu lieu il y a longtemps; c'est pourquoi je suis un peu flou. Je peux trouver quelques notes si c'est important.)

Dès le départ, nous recherchions a) des threads natifs et b) la prise en charge de Linux, Mac et Windows. Ces deux conditions combinées ont frappé tout le monde mais (je pense) Allegro et Chez out - donc afin de poursuivre l'évaluation, nous avons dû assouplir l'exigence du multi-threading.

Nous avons rassemblé une suite de petits programmes et les avons utilisés pour l'évaluation et les tests. Cela a révélé un certain nombre de problèmes. Par exemple: certaines implémentations présentaient des défauts qui empêchaient l'exécution de certains tests; certaines implémentations ne pouvaient pas compiler de code au moment de l'exécution; certaines implémentations ne pouvaient pas facilement intégrer du code compilé au moment de l'exécution avec du code précompilé; certaines implémentations avaient des collecteurs d'ordures qui étaient clairement meilleurs (ou clairement pires) que les autres; etc.

Pour nos besoins, seules les trois implémentations commerciales - Allegro, Chez et Lispworks - ont réussi nos tests primaires. Sur les trois, seul Chez a réussi tous les tests avec brio. À l'époque, je pense que Lispworks n'avait de threads natifs sur aucune plate-forme (je pense qu'ils en ont maintenant) et je pense qu'Allegro n'avait que des threads natifs sur certaines plates-formes. De plus, Allegro avait des frais de licence d'exécution "appelez-nous" que je n'aimais pas beaucoup. Je crois que Lispworks n'avait aucun frais d'exécution et Chez avait un arrangement simple (et très raisonnable) (et il n'a démarré que si vous avez utilisé le compilateur au moment de l'exécution).

Après avoir produit des morceaux de code quelque peu importants dans LISP et Scheme, voici quelques points de comparaison et de contraste:

  • Les environnements LISP sont beaucoup plus matures. Vous en avez beaucoup plus pour votre argent. (Cela dit, plus de code équivaut également à plus de bugs.)

  • Les environnements LISP sont beaucoup plus difficiles à apprendre. Vous avez besoin de beaucoup plus de temps pour devenir compétent; LISP commun est un langage énorme - et c'est avant d'arriver aux bibliothèques que les implémentations commerciales ajoutent en plus. (Cela dit, le cas de syntaxe de Scheme est beaucoup plus subtil et compliqué que n'importe quoi dans LISP.)

  • Les environnements LISP peuvent être un peu plus difficiles à produire dans les binaires. Vous devez "secouer" votre image pour supprimer les bits inutiles, et si vous n'exercez pas correctement votre programme pendant ce processus, vous pourriez vous retrouver avec des erreurs d'exécution plus tard. . En revanche, avec Chez, nous compilons un fichier de niveau supérieur qui comprend tous les autres fichiers dont il a besoin et nous avons terminé.

J'ai déjà dit que nous avons fini par utiliser Scheme dans un certain nombre d'endroits où nous n'avions pas l'intention de le faire à l'origine. Pourquoi? Je peux penser à trois raisons du haut de ma tête.

Tout d'abord, nous avons appris à faire confiance à Chez (et à son développeur, Cadence). Nous avons beaucoup demandé à l'outil, et il a toujours livré. Par exemple, Chez a historiquement eu un nombre de défauts insignifiant, et son gestionnaire de mémoire a été très, très bon.

Deuxièmement, nous avons appris à aimer la performance de Chez. Nous utilisions quelque chose qui ressemblait à un langage de script - et nous en tirions une vitesse de code natif. Pour certaines choses qui n'avaient pas d'importance - mais cela ne faisait jamais de mal, et parfois cela aidait énormément.

Troisièmement, nous avons appris à aimer le schéma d'abstraction. Je ne parle pas seulement des macros, au fait; Je veux dire des choses comme les fermetures, les lambdas, les appels de queue, etc. Une fois que vous commencez à penser en ces termes, d'autres langues semblent plutôt limitées en comparaison.

Le schéma est-il parfait? Non; c'est un compromis. Tout d'abord, cela permet aux développeurs individuels d'être plus efficaces - mais il est plus difficile pour les développeurs de se fouiller le code les uns des autres car les panneaux indicateurs que la plupart des langues ont (par exemple, pour les boucles) sont manquants dans Scheme (par exemple, il y a un million de façons de le faire une boucle for). Deuxièmement, il y a un bassin beaucoup plus restreint de développeurs avec qui parler, recruter, emprunter, etc.

Pour résumer, je pense que je dirais: LISP et Scheme offrent des capacités qui ne sont pas largement disponibles ailleurs. Cette capacité est un compromis, il vaut donc mieux que ce soit logique dans votre cas particulier. Dans notre cas, les facteurs déterminants entre l'utilisation de LISP ou de Scheme avaient plus à voir avec des fonctionnalités très fondamentales (prise en charge de la plateforme, threads de plateforme, compilation au moment de l'exécution, licence au moment de l'exécution) qu'avec les fonctionnalités de langue ou de bibliothèque. Encore une fois, dans notre cas, c'était aussi un compromis: avec Chez, nous avons obtenu les fonctionnalités de base que nous voulions, mais nous avons perdu les bibliothèques étendues des environnements commerciaux LISP.

Aussi, juste pour réitérer: nous avons regardé les différents Lisps et Schémas il y a longtemps; ils ont tous évolué et se sont améliorés depuis.

102
Michael Lenaghan

Je n'aime généralement pas coller un lien comme réponse, mais j'ai écrit un article de blog sur ce sujet. Il n'est pas exhaustif, mais il fait passer certains des points principaux.

http://symbo1ics.com/blog/?p=729

Edit: Voici les points principaux:

  1. [~ # ~] existence [~ # ~] : Les deux lisps sont venus après un tas d'autres lisps. Le schéma a pris la voie axiomatique minimale. CL a emprunté la voie baroque.
  2. [~ # ~] cas [~ # ~] : le schéma est généralement sensible à la casse. CL n'est pas (bien qu'il puisse l'être). C'est parfois manqué, mais son aspect pratique est débattu (par moi).
  3. [~ # ~] noms [~ # ~] : Les noms des symboles en CL sont souvent étranges et déroutants. TERPRI, PROGN, etc. Le schéma a généralement des noms très sensés. C'est quelque chose qui manque dans CL.
  4. [~ # ~] fonctions [~ # ~] : CL a un espace de noms de fonction séparé. C'est pas manqué dans Scheme. Avoir un seul espace de noms permet généralement une programmation fonctionnelle très propre, ce qui est souvent difficile ou gênant en CL. Mais cela a un coût --- vous devez parfois masquer des noms comme "list" en "lst" dans Scheme.
  5. Macros [~ # ~] [~ # ~] : les macros sales de bas niveau me manquent le plus dans Scheme. Ouais, syntax-rules est très bien et dandy jusqu'à ce que vous vouliez vraiment pirater certaines choses. En revanche, les macros hygiéniques sont parfois manquées en CL. Ne pas avoir de méthode standard pour les faire signifie réinventer la roue.
  6. Portabilité [~ # ~] [~ # ~] : Il arrive souvent que CL soit plus portable, malgré la standardisation des deux langues. CL est plus grand, et il y a donc plus de fonctionnalités standard à utiliser sans bibliothèques externes. Cela signifie également que des tâches plus dépendantes de l'implémentation peuvent être effectuées de manière portative. En outre, Scheme souffre d'avoir un billion d'implémentations, dont la plupart sont quelque peu incompatibles. Cela rend CL très souhaitable.
  7. [~ # ~] bibliothèques [~ # ~] : Très lié à mon dernier point. Le régime a des IFRS mais n'est pas universellement reconnu. Il n'y a aucun moyen portable de travailler avec les bibliothèques. CL d'autre part a des moyens. Et Quicklisp est un cadeau de Dieu (Xach) --- une sorte de référentiel de bibliothèques à utiliser.
  8. [~ # ~] implémentations [~ # ~] : le schéma souffre d'avoir autant d'implémentations. Il n'y a pas de véritable implémentation canonique. CL d'autre part a quelques implémentations très performantes ou à usage spécifique (hautes performances: SBCL, commerciales: Allegro, embarquées: ECL, portables: CLISP, Java: ABCL, ...).

Bien que je n'ai parlé qu'à la première personne un peu plus haut, il devrait être clair ce que je manque et ce que je ne fais pas.

[Je m'excuse si elles sont trop générales. Il semble que vous souhaitiez peut-être des détails beaucoup plus précis. Il y a quelques détails dans le post.]

37
Quadrescence

J'ai récemment commencé un projet à domicile en utilisant une bibliothèque qui a une version C et une version Java. Je voulais utiliser LISP pour le projet, et j'ai passé environ un mois à hésiter entre l'utilisation de Common LISP, Scheme ou Clojure. J'ai une certaine expérience avec les trois, mais seulement des projets de jouets. Je vais vous parler un peu de mon expérience avec chacun d'eux avant de vous dire lequel j'ai fini par choisir.

PLT Racket a un Nice IDE qui vous permet non seulement d'évaluer les expressions de l'éditeur, mais aussi de taper des crochets au lieu de parens, en les repassant en parens le cas échéant. Racket a également un grand ensemble de bibliothèques avec l'installation et encore plus disponible en téléchargement. Le débogueur visuel est également utile.

Mon implémentation LISP commune (SBCL) n'a pas d'IDE, mais elle est habituelle avec les implémentations CL open source pour utiliser Emacs et SLIME. Cette combinaison peut être très efficace. Outre la possibilité d'évaluer les expressions lorsque vous les tapez dans le fichier source, il existe également un REPL qui contient toutes les commandes d'édition d'emacs, de sorte que la copie de code peut se faire efficacement dans les deux sens. Même les objets affichés dans le tampon REPL peuvent être copiés et collés. Alt+( et Alt+) sont efficaces pour traiter les parenthèses et les retraits appariés.

Toutes les fonctionnalités Emacs ci-dessus sont également disponibles pour Clojure. Mon expérience de montage avec Clojure est similaire à celle de LISP. L'interopération Java a bien fonctionné, et j'aimerais faire un projet Clojure une fois arrivé à maturité.

J'ai pu accéder à la bibliothèque en utilisant les trois (Common LISP, Racket et Clojure), mais j'ai fini par choisir Common LISP pour le projet. Le facteur décisif était que le FFI était beaucoup plus facile à utiliser dans le LISP commun. CFFI a un très bon manuel avec un exemple de code et des explications détaillées de chaque méthode. J'ai pu envelopper 20 fonctions C en un après-midi et je n'ai pas eu à toucher au code depuis.

L'autre facteur est que je connais mieux LISP commun que Clojure ou R6RS Scheme. J'ai lu la plupart des livres Practical Common LISP et Graham, et je suis à l'aise avec l'Hyperspec. Ce n'est pas encore un code très "lispy", mais je suis sûr que cela changera à mesure que j'acquerrai de l'expérience.

25
Larry Coleman

Je programme en CL et en Racket.

Je suis en train de développer un site Web dans Common LISP, et j'ai écrit une suite de programmes internes pour mon ancien employeur dans Racket.

Pour le code interne, j'ai choisi Racket (alors connu sous le nom de PLT Scheme) parce que l'employeur était un magasin Windows et je ne pouvais pas leur faire payer LispWorks. La seule bonne implémentation CL open-source pour Windows était (et est toujours) CCL, ce qui nécessite SSE support dans le processeur. L'employeur, étant bon marché, utilisait Matériel de l'âge de pierre. Même si l'employeur disposait d'un matériel décent, la seule bibliothèque GUI de conséquence dans Common LISP est McCLIM, qui ne fonctionne que sur Unix. Racket a une bonne bibliothèque GUI qui fonctionne à la fois sur Unix et Windows, ce qui était essentiel pour mon succès du projet.

J'ai passé plus d'un an à supporter l'éditeur primitif DrRacket. EMACS n'a pas pu transformer la version GUI de Racket, alors connue sous le nom de MrEd, en un LISP inférieur sur Windows. J'ai dû faire sans pouvoir évaluer l'expression au curseur avec une seule touche. Au lieu de cela, j'ai dû sélectionner manuellement l'expression S, la copier, cliquer sur la fenêtre REPL (car il n'y a pas de touche pour y basculer), puis coller l'expression S. J'ai également devait se passer d'un éditeur qui pourrait me montrer les arguments attendus de la fonction ou de la macro que j'utilisais. DrRacket ne remplace pas SLIME.

L'employeur utilisait une base de données propriétaire avec une API XML compliquée qui nécessitait des charges d'informations apparemment inutiles pour pouvoir répondre à sa version d'une requête SELECT. J'ai décidé d'utiliser HTMLPrag à la fois pour émettre du XML vers cette API et pour analyser les réponses. Cela a très bien fonctionné.

J'ai dû apprendre le système de macro "cas de syntaxe" trop compliqué de Racket afin d'écrire une macro qui me permettrait d'interagir avec l'API XML trop compliquée en tapant des formulaires qui ressemblaient à SQL. Cette partie aurait été beaucoup plus facile si j'avais DEFMACRO à ma disposition. Cependant, le résultat final était toujours homogène même s'il a fallu plus d'efforts pour y parvenir.

De plus, je devais me passer de la macro LOOP de Common LISP. Racket n'a commencé à fournir une alternative qu'après avoir écrit la plupart du code, et l'alternative est toujours aussi mauvaise que LOOP (même si l'équipe de développement de Racket insiste sur le fait que c'est mieux - ils ont tout simplement tort). J'ai fini par écrire beaucoup de formulaires nommés LET qui utilisaient "car" et "cdr" pour parcourir les listes.

En parlant de voiture et de cdr, rien n'est plus frustrant que l'interprétation de Scheme de (car '()) comme étant une erreur. J'ai profité de la sensibilité à la casse de Racket et j'ai implémenté CAR et CDR, qui ont la sémantique Common LISP. Cependant, la séparation de '() et #f rend beaucoup moins utile le retour de' () comme valeur par défaut.

J'ai également fini par réimplémenter UNWIND-PROTECT et j'ai inventé mon propre système de redémarrage pour combler le vide laissé par Racket. La communauté Racket doit apprendre que les redémarrages sont très utiles et faciles à implémenter.

La forme des valeurs let de Racket était trop verbeuse, j'ai donc implémenté MULTIPLE-VALUE-BIND. C'était absolument nécessaire, car Racket nécessite vous de recevoir tous les valeurs qui sont générées, que vous les utilisiez ou non.

Plus tard, j'ai essayé d'écrire un client API XML eBay dans Common LISP, seulement pour découvrir qu'il n'avait rien comme HTMLPrag. HTMLPrag est très utile. J'ai fini par faire ce projet dans Racket. J'ai expérimenté les installations de programmation littéraire de Racket, pour découvrir que je suis le seul programmeur sur Terre qui trouve le code alphabétisé correctement écrit plus difficile à éditer que le code ordinaire, ou le code alphabétique "commentaires excessifs" mal écrit.

Mon nouveau projet se fait dans Common LISP, ce qui était le bon choix car la communauté Racket ne croit tout simplement pas au parallélisme, qui est essentiel pour ce projet. La seule chose que je pensais avoir ratée de Racket était la suite. Cependant, j'ai pu faire ce dont j'avais besoin en utilisant les redémarrages et, rétrospectivement, j'aurais probablement pu le faire avec une simple fermeture.

21
Racketeer

Le schéma est conçu avec une compilation séparée à l'esprit. En conséquence, la puissance de ses macros est souvent fortement limitée, même avec les extensions qui permettent un défmacro de style LISP commun au lieu d'un système de macro hygiénique médiocre et limité. Il n'est pas toujours possible de définir une macro qui définit une autre macro, destinée à une utilisation immédiate dans une prochaine ligne de code. Et une telle possibilité est essentielle pour mettre en œuvre des compilateurs eDSL efficaces.

Inutile de mentionner que les implémentations de Scheme avec uniquement des macros hygiéniques R5RS sont à peine utiles pour moi, car mon style de métaprogrammation ne peut pas être correctement traduit en hygiène.

Heureusement, il existe des implémentations Scheme (par exemple, Racket) qui n'ont pas cette limitation.

5
SK-logic