Haskell, Lisp et verbosité [fermé]

104

Pour ceux d'entre vous expérimentés à la fois dans Haskell et dans une certaine saveur de Lisp, je suis curieux de savoir à quel point il est «agréable» (pour utiliser un terme horrible) d'écrire du code en Haskell contre Lisp.

Un peu de contexte: j'apprends Haskell maintenant, après avoir travaillé avec Scheme et CL (et une petite incursion dans Clojure). Traditionnellement, vous pourriez me considérer comme un fan des langages dynamiques pour la brièveté et la rapidité qu'ils offrent. Je suis rapidement tombé amoureux des macros Lisp, car cela me donnait un autre moyen d'éviter la verbosité et le passe-partout.

Je trouve Haskell incroyablement intéressant, car il me présente des méthodes de codage dont je ne savais pas qu'elles existaient. Il a certainement certains aspects qui semblent aider à atteindre l'agilité, comme la facilité d'écriture de fonctions partielles. Cependant, je suis un peu préoccupé par la perte des macros Lisp (je suppose que je les perds; à vrai dire, je n'ai peut-être tout simplement pas encore appris à leur sujet?) Et le système de typage statique.

Est-ce que quelqu'un qui a fait une quantité décente de codage dans les deux mondes voudrait dire comment les expériences diffèrent, ce que vous préférez et si cette préférence est situationnelle?

J Cooper
la source

Réponses:

69

Réponse courte:

  • presque tout ce que vous pouvez faire avec des macros, vous pouvez le faire avec une fonction d'ordre supérieur (et j'inclus des monades, des flèches, etc.), mais cela pourrait demander plus de réflexion (mais seulement la première fois, et c'est amusant et vous serez un meilleur programmeur pour cela), et
  • le système statique est suffisamment général pour ne jamais vous gêner et, de façon quelque peu surprenante, il "aide à atteindre l'agilité" (comme vous l'avez dit) parce que lorsque votre programme se compile, vous pouvez être presque certain que c'est correct, donc cette certitude vous permet d'essayer des choses que vous pourriez avoir peur d'essayer - il y a une sensation "dynamique" dans la programmation bien que ce ne soit pas la même chose qu'avec Lisp.

[Remarque: il existe un " Template Haskell " qui vous permet d'écrire des macros comme en Lisp, mais à proprement parler, vous ne devriez jamais en avoir besoin .]

ShreevatsaR
la source
15
De Conor McBride, cité par Don Stewart: «J'aime penser que les types déforment notre gravité, de sorte que la direction dont nous avons besoin pour voyager [pour écrire des programmes corrects] devienne« descendante ».» Le système de types rend étonnamment facile d'écrire des programmes corrects… voir ce post et ses re-partages.
ShreevatsaR
3
Les fonctions d'ordre élevé ne peuvent pas remplacer les macros, et en fait, CL a les deux pour une raison quelconque. Le vrai pouvoir des macros en CL est qu'elles permettent au développeur d'introduire de nouvelles fonctionnalités de langage qui aident à mieux exprimer la solution à un problème, sans avoir à attendre une nouvelle version du langage comme en Haskell ou Java. Par exemple, si Haskell avait ce pouvoir, les auteurs Haskell n'auraient pas besoin d'écrire des extensions GHC, si elles pourraient être implémentées par les développeurs eux-mêmes sous forme de macros à tout moment.
mljrg
@mljrg Avez-vous un exemple concret? Voir les commentaires sur la réponse d'Hibou57 ci-dessous où un exemple allégué s'est avéré douteux. Je serais intéressé de savoir le genre de chose que vous voulez dire (par exemple le code Haskell avec et sans macros).
ShreevatsaR
4
Sortez le curry de Haskell. Pourriez-vous l'implémenter avec ce qui resterait à Haskell? Autre exemple: supposons que Haskell ne supporte pas la correspondance de modèles, pourriez-vous l'ajouter vous-même sans que les développeurs de GHC le prennent en charge? Dans CL, vous pouvez utiliser des macros pour étendre le langage à volonté. Je suppose que c'est pourquoi CL le langage n'a pas changé depuis son standard dans les années 90, alors que Haskell semble avoir un flux sans fin d'extensions dans GHC.
mljrg le
64

Tout d'abord, ne vous inquiétez pas de perdre des fonctionnalités particulières comme la saisie dynamique. Comme vous connaissez Common Lisp, un langage remarquablement bien conçu, je suppose que vous êtes conscient qu'un langage ne peut pas être réduit à son ensemble de fonctionnalités. Il s'agit d'un tout cohérent, n'est-ce pas?

À cet égard, Haskell brille tout aussi brillamment que Common Lisp. Ses fonctionnalités se combinent pour vous offrir un moyen de programmation qui rend le code extrêmement court et élégant. Le manque de macros est quelque peu atténué par des concepts plus élaborés (mais également plus difficiles à comprendre et à utiliser) comme les monades et les flèches. Le système de type statique ajoute à votre puissance plutôt que de vous gêner comme il le fait dans la plupart des langages orientés objet.

D'un autre côté, la programmation en Haskell est beaucoup moins interactive que Lisp, et l'énorme quantité de réflexion présente dans des langages comme Lisp ne correspond tout simplement pas à la vision statique du monde que Haskell présuppose. Les jeux d'outils mis à votre disposition sont donc assez différents entre les deux langues, mais difficiles à comparer.

Personnellement, je préfère la manière de programmer Lisp en général, car je pense que cela correspond mieux à ma façon de travailler. Cependant, cela ne signifie pas que vous êtes obligé de le faire également.

Matthias Benkard
la source
4
Pourriez-vous élaborer un peu plus sur "la programmation en Haskell est beaucoup moins interactive". GHCi ne fournit-il pas vraiment tout ce dont vous avez besoin?
Johannes Gerer
3
@JohannesGerer: Je ne l'ai pas essayé, mais d'après ce que j'ai lu, GHCi n'est pas un shell dans l'image en cours d'exécution, où vous pouvez redéfinir et étendre des parties arbitraires du programme entier pendant qu'il est en cours d'exécution. De plus, la syntaxe Haskell rend beaucoup plus difficile la copie de fragments de programme entre le repl et l'éditeur par programmation.
Svante
13

Il y a moins besoin de métaprogrammation dans Haskell que dans Common Lisp car beaucoup peuvent être structurés autour de monades et la syntaxe ajoutée fait que les DSL embarqués ressemblent moins à un arbre, mais il y a toujours Template Haskell, comme mentionné par ShreevatsaR , et même Liskell (sémantique Haskell + Lisp syntaxe) si vous aimez les parenthèses.

Herrmann
la source
1
le lien Liskell est mort, mais ces jours-ci, il y a Hackett .
Will Ness
10

Concernant les macros, voici une page qui en parle: Hello Haskell, Goodbye Lisp . Il explique un point de vue où les macros ne sont tout simplement pas nécessaires dans Haskell. Il est livré avec un court exemple de comparaison.

Exemple de cas où une macro LISP est requise pour éviter l'évaluation des deux arguments:

(defmacro doif (x y) `(if ,x ,y))

Exemple de cas où Haskell n'évalue pas systématiquement les deux arguments, sans avoir besoin de quoi que ce soit comme une définition de macro:

doif x y = if x then (Just y) else Nothing

Et voilà

Hibou57
la source
17
C'est une idée fausse courante. Oui, dans Haskell, la paresse signifie que vous n'avez pas besoin de macros lorsque vous voulez éviter d'évaluer certaines parties d'une expression, mais ce ne sont que le sous-ensemble le plus trivial de toutes les utilisations de macro. Google pour "The Swine Before Perl" pour une présentation démontrant une macro qui ne peut pas être faite avec paresse. En outre, si vous ne voulez un peu d'être stricte, alors vous ne pouvez le faire en fonction - en miroir le fait que le schéma de delayne peut pas être une fonction.
Eli Barzilay
8
@Eli Barzilay: Je ne trouve pas cet exemple très convaincant. Voici une traduction Haskell complète et simple de la diapositive 40: pastebin.com/8rFYwTrE
Reid Barton
5
@Eli Barzilay: Je ne comprends pas du tout votre réponse. accept est le (E) DSL. La acceptfonction est l'analogue de la macro décrite dans les pages précédentes, et la définition de vest exactement parallèle à la définition de vdans Scheme sur la diapositive 40. Les fonctions Haskell et Scheme calculent la même chose avec la même stratégie d'évaluation. Au mieux, la macro vous permet d'exposer davantage la structure de votre programme à l'optimiseur. Vous pouvez difficilement prétendre cela comme un exemple où les macros augmentent la puissance expressive du langage d'une manière non reproduite par une évaluation paresseuse.
Reid Barton
5
@Eli Barzilay: Dans un schéma paresseux hypothétique, vous pouvez écrire ceci: pastebin.com/TN3F8VVE Ma déclaration générale est que cette macro vous achète très peu: une syntaxe légèrement différente et un temps plus facile pour l'optimiseur (mais cela n'aurait pas d'importance un "compilateur suffisamment intelligent"). En échange, vous vous êtes enfermé dans un langage inexpressif; comment définir un automate qui correspond à n'importe quelle lettre sans toutes les énumérer? De plus, je ne sais pas ce que vous entendez par "l'utiliser dans toutes les sous-listes" ou "l'utilisation requise de where avec sa propre portée".
Reid Barton
15
OK, j'abandonne. Apparemment, votre définition de DSL est "les arguments d'une macro" et donc mon exemple de Scheme paresseux n'est pas un DSL, bien qu'il soit syntaxiquement isomorphe à l'original ( automatondevenir letrec, :devenir accept, ->devenir rien dans cette version). Peu importe.
Reid Barton
9

Je suis un programmeur Common Lisp.

Ayant essayé Haskell il y a quelque temps, mon résultat personnel était de m'en tenir à CL.

Les raisons:

  • typage dynamique (consultez Typage dynamique ou statique - Une analyse basée sur des modèles par Pascal Costanza )
  • arguments optionnels et mots-clés
  • syntaxe de liste homoiconique uniforme avec macros
  • syntaxe de préfixe (pas besoin de se souvenir des règles de priorité)
  • impur et donc plus adapté au prototypage rapide
  • système d'objet puissant avec protocole méta-objet
  • norme mature
  • large gamme de compilateurs

Haskell a bien sûr ses propres mérites et fait certaines choses d'une manière fondamentalement différente, mais cela ne résout pas le problème à long terme pour moi.

Leslie P. Polzer
la source
Hé, avez-vous le titre de cet article de Costanza auquel vous êtes lié? Il semble que ce fichier a été déplacé.
michiakig
2
Notez que haskell prend également en charge la syntaxe du préfixe, mais je dirais que monade >> = serait très très moche de l'utiliser. De plus, je ne suis pas d'accord avec le fait que l'impureté soit une bénédiction: P
alternative
2
J'aime cette note d'accompagnement: nous n'avons pas encore rassemblé de données empiriques sur la question de savoir si ce problème cause de graves problèmes dans les programmes du monde réel.
Jerome Baum
22
Aucun des exemples de cet article (Pascal Costanza, Dynamic vs. Static Typing - A Pattern-Based Analysis ) ne s'applique à Haskell. Ils sont tous spécifiques à Java (ou plus précisément, spécifiques à la "programmation orientée objet" ) et je ne vois aucun de ces problèmes se poser dans Haskell. De même, tous vos autres arguments sont discutables: on peut aussi dire que Haskell est "pur et donc plus adapté au prototypage rapide", que la syntaxe de préfixe n'est pas obligatoire, qu'il n'a pas un large éventail de compilateurs qui font des choses différentes , etc.
ShreevatsaR
11
Ce document est en effet presque totalement hors de propos pour Haskell. " dilbert = dogbert.hire(dilbert);" ?? Je doute que de nombreux programmeurs Haskell puissent même lire ceci sans trembler un peu.
gauche vers le
6

Dans Haskell, vous pouvez définir une fonction if, ce qui est impossible dans LISP. Cela est possible en raison de la paresse, qui permet une plus grande modularité dans les programmes. Cet article classique: Pourquoi la PF compte par John Hughes, explique comment la paresse améliore la composabilité.

luntain
la source
5
Scheme (l'un des deux principaux dialectes LISP) a en fait une évaluation paresseuse, bien que ce ne soit pas par défaut comme dans Haskell.
Randy Voet
7
(defmacro doif (xy) `(if, x, y))
Joshua Cheek
4
Une macro n'est pas la même chose qu'une fonction - les macros ne fonctionnent pas bien avec les fonctions d'ordre supérieur comme fold, par exemple, alors que les fonctions non strictes le font .
Tikhon Jelvis
5

Il y a des choses vraiment cool que vous pouvez réaliser en Lisp avec des macros qui sont encombrantes (si possible) dans Haskell. Prenons par exemple la macro `mémoize '(voir le chapitre 9 du PAIP de Peter Norvig). Avec lui, vous pouvez définir une fonction, disons foo, puis simplement évaluer (mémoriser 'foo), qui remplace la définition globale de foo par une version mémorisée. Pouvez-vous obtenir le même effet dans Haskell avec des fonctions d'ordre supérieur?


la source
3
Pas tout à fait (AFAIK), mais vous pouvez faire quelque chose de similaire en modifiant la fonction (en supposant qu'elle soit récursive) pour prendre la fonction à appeler récursivement en tant que paramètre (!) Plutôt que de simplement s'appeler par son nom: haskell.org/haskellwiki/Memoization
j_random_hacker
6
Vous pouvez ajouter foo à une structure de données paresseuse, où la valeur sera stockée une fois calculée. Ce sera effectivement la même chose.
Theo Belaire
Tout dans Haskell est mémorisé et probablement intégré lorsque cela est nécessaire par défaut par le compilateur Haskell.
aoeu256
4

Alors que je continue mon parcours d'apprentissage Haskell, il semble qu'une chose qui aide à "remplacer" les macros est la possibilité de définir vos propres opérateurs d'infixe et de personnaliser leur priorité et leur associativité. Un peu compliqué, mais un système intéressant!

J Cooper
la source