web-dev-qa-db-fra.com

Quel est le problème avec Haskell?

Je connais quelques programmeurs qui continuent de parler de Haskell quand ils sont entre eux, et ici sur SO tout le monde semble aimer ce langage. Être bon chez Haskell semble un peu comme la marque d'un programmeur de génie.

Quelqu'un peut-il donner quelques exemples Haskell qui montrent pourquoi il est si élégant/supérieur?

107
Frank

La façon dont cela m'a été présenté, et ce que je pense être vrai après avoir travaillé sur Haskell pendant un mois maintenant, c'est le fait que la programmation fonctionnelle tord votre cerveau de manière intéressante: elle vous oblige à penser aux problèmes familiers de différentes manières : au lieu de boucles, pensez aux cartes et aux plis et filtres, etc. En général, si vous avez plus d'une perspective sur un problème, cela vous permet de mieux raisonner sur ce problème et de changer de point de vue si nécessaire.

L'autre chose vraiment intéressante à propos de Haskell est son système de type. Il est strictement typé, mais le moteur d'inférence de type donne l'impression que c'est un programme Python qui vous indique comme par magie quand vous avez fait une erreur stupide liée au type. Les messages d'erreur de Haskell à cet égard manquent quelque peu , mais au fur et à mesure que vous vous familiariserez avec la langue, vous vous direz: voilà ce que la frappe est censée être!

131
Edward Z. Yang

C'est l'exemple qui m'a convaincu d'apprendre Haskell (et je suis content de l'avoir fait).

-- program to copy a file --
import System.Environment

main = do
         --read command-line arguments
         [file1, file2] <- getArgs

         --copy file contents
         str <- readFile file1
         writeFile file2 str

OK, c'est un programme court et lisible. En ce sens, c'est mieux qu'un programme C. Mais en quoi est-ce si différent d'un programme (=) Python avec une structure très similaire?

La réponse est une évaluation paresseuse. Dans la plupart des langues (même certaines fonctionnelles), un programme structuré comme celui ci-dessus entraînerait le chargement de tout le fichier en mémoire, puis l'écriture à nouveau sous un nouveau nom.

Haskell est "paresseux". Il ne calcule pas les choses jusqu'à ce qu'il en ait besoin, et par extension ne fait pas calcule les choses dont il n'a jamais besoin. Par exemple, si vous supprimiez la ligne writeFile, Haskell ne prendrait pas la peine de lire quoi que ce soit du fichier en premier lieu.

En l'état, Haskell se rend compte que le writeFile dépend du readFile, et est donc en mesure d'optimiser ce chemin de données.

Bien que les résultats dépendent du compilateur, ce qui se produit généralement lorsque vous exécutez le programme ci-dessus est le suivant: le programme lit un bloc (disons 8 Ko) du premier fichier, puis l'écrit dans le deuxième fichier, puis lit un autre bloc du premier fichier, et l'écrit dans le deuxième fichier, etc. (Essayez d'exécuter strace dessus!)

... qui ressemble beaucoup à ce que ferait l'implémentation C efficace d'une copie de fichier.

Ainsi, Haskell vous permet d'écrire des programmes compacts et lisibles - souvent sans sacrifier beaucoup de performances.

Une autre chose que je dois ajouter est que Haskell rend tout simplement difficile l'écriture de programmes bogués. Le système de type étonnant, le manque d'effets secondaires et bien sûr la compacité du code Haskell réduit les bugs pour au moins trois raisons:

  1. Meilleure conception du programme. Une complexité réduite conduit à moins d'erreurs logiques.

  2. Code compact. Moins de lignes sur lesquelles les bogues existent.

  3. Compiler les erreurs. Beaucoup de bugs juste Haskell n'est pas valide.

Haskell n'est pas pour tout le monde. Mais tout le monde devrait essayer.

135
Artelius

Vous posez en quelque sorte la mauvaise question.

Haskell n'est pas une langue où vous allez regarder quelques exemples sympas et dire "aha, je vois maintenant, c'est ce qui le rend bien!"

C'est plus comme, nous avons tous ces autres langages de programmation, et ils sont tous plus ou moins similaires, et puis il y a Haskell qui est totalement différent et farfelu d'une manière qui est totalement génial une fois que vous vous êtes habitué au farfelu. Mais le problème est qu'il faut un certain temps pour s'acclimater à la folie. Des choses qui distinguent Haskell de presque tous les autres langages, même semi-traditionnels:

  • Évaluation paresseuse
  • Aucun effet secondaire (tout est pur, IO/etc se produit via des monades)
  • Système de type statique incroyablement expressif

ainsi que certains autres aspects qui sont différents de nombreuses langues traditionnelles (mais partagés par certains):

  • fonctionnel
  • espace important
  • type déduit

Comme certaines autres affiches ont répondu, la combinaison de toutes ces fonctionnalités signifie que vous envisagez la programmation d'une manière entièrement différente. Et il est donc difficile de trouver un exemple (ou un ensemble d'exemples) qui communique correctement cela à Joe-mainstream-programmer. C'est une chose expérientielle. (Pour faire une analogie, je peux vous montrer des photos de mon voyage en Chine en 1970, mais après avoir vu les photos, vous ne savez toujours pas comment c'était d'avoir vécu là-bas pendant cette période. De même, je peux vous montrer un Haskell 'quicksort', mais vous ne saurez toujours pas ce que signifie être un Haskeller.)

65
Brian

Ce qui distingue vraiment Haskell, c'est l'effort qu'il fait dans sa conception pour appliquer la programmation fonctionnelle. Vous pouvez programmer dans un style fonctionnel dans à peu près n'importe quelle langue, mais il est trop facile d'abandonner à la première convenance. Haskell ne vous permet pas d'abandonner la programmation fonctionnelle, vous devez donc l'amener à sa conclusion logique, qui est un programme final plus facile à raisonner, et évite toute une classe des types de bugs les plus épineux.

Quand il s'agit d'écrire un programme pour une utilisation réelle, vous pouvez trouver Haskell manquant d'une manière pratique, mais votre solution finale sera meilleure pour avoir connu Haskell pour commencer. Je n'y suis définitivement pas encore, mais jusqu'à présent, apprendre Haskell a été beaucoup plus instructif que dire, LISP était à l'université.

26
gtd

Une partie du problème est que la pureté et le typage statique permettent un parallélisme combiné à des optimisations agressives. Les langages parallèles sont chauds maintenant, le multicœur étant un peu perturbateur.

Haskell vous offre plus d'options pour le parallélisme que n'importe quel langage à usage général, ainsi qu'un compilateur de code natif rapide. Il n'y a vraiment aucune concurrence avec ce type de support pour les styles parallèles:

Donc, si vous voulez faire fonctionner votre multicœur, Haskell a quelque chose à dire. Un bon endroit pour commencer est avec Simon Peyton Jones ' tutoriel sur la programmation parallèle et simultanée dans Haskell .

22
Don Stewart

J'ai passé la dernière année à apprendre Haskell et à y écrire un projet assez vaste et complexe. (Le projet est un système de négociation d'options automatisé, et tout, des algorithmes de négociation à l'analyse et à la gestion des flux de données de marché à bas niveau et à grande vitesse, est effectué dans Haskell.) Il est beaucoup plus concis et plus facile à comprendre (pour ceux qui ont arrière-plan approprié) qu’une version Java version serait, ainsi que extrêmement robuste.

La plus grande victoire pour moi a probablement été la capacité de modulariser le flux de contrôle à travers des choses telles que les monoïdes, les monades, etc. Un exemple très simple serait le monoïde de commande; dans une expression telle que

c1 `mappend` c2 `mappend` c3

c1 et ainsi de suite retour LT, EQ ou GT, c1 renvoyant EQ provoque la poursuite de l'expression, évaluant c2; si c2 renvoie LT ou GT c'est la valeur de l'ensemble, et c3 n'est pas évalué. Ce genre de choses devient beaucoup plus sophistiqué et complexe dans des choses comme les générateurs de messages monariques et les analyseurs syntaxiques où je peux transporter différents types d'état, avoir des conditions d'abandon variables, ou vouloir décider pour un appel particulier si l'avortement signifie vraiment "Pas de traitement supplémentaire" ou signifie "retourner une erreur à la fin, mais continuer le traitement pour collecter d'autres messages d'erreur".

C'est tout ce qu'il faut du temps et probablement beaucoup d'efforts pour apprendre, et donc il peut être difficile de faire un argument convaincant pour ceux qui ne connaissent pas déjà ces techniques. Je pense que le tutoriel All About Monads donne une démonstration assez impressionnante d'une facette de cela, mais je ne m'attendrais pas à ce que quiconque ne connaissant pas déjà le matériel "l'obtienne" le premier, ou même la troisième, une lecture attentive.

Quoi qu'il en soit, il y a aussi beaucoup d'autres bonnes choses à Haskell, mais c'est un problème majeur que je ne vois pas mentionné si souvent, probablement parce que c'est plutôt complexe.

18
Curt J. Sampson

Software Transactional Memory est un moyen plutôt cool de gérer la concurrence. Il est beaucoup plus flexible que la transmission de messages et n'est pas sujet à une impasse comme les mutex. GHC l'implémentation de STM est considérée comme l'une des meilleures.

17
bmdhacks

Pour un exemple intéressant, vous pouvez consulter: http://en.literateprograms.org/Quicksort_ (Haskell)

Ce qui est intéressant, c'est de regarder l'implémentation dans différentes langues.

Ce qui rend Haskell si intéressant, avec d'autres langages fonctionnels, c'est le fait que vous devez penser différemment à la façon de programmer. Par exemple, vous n'utiliserez généralement pas de boucles for ou while, mais vous utiliserez la récursivité.

Comme mentionné ci-dessus, Haskell et d'autres langages fonctionnels Excel avec des applications de traitement et d'écriture parallèles pour travailler sur plusieurs cœurs.

11
James Black

Je ne pourrais pas vous donner un exemple, je suis un gars OCaml, mais quand je suis dans une situation telle que vous, la curiosité s'installe et je dois télécharger un compilateur/interprète et l'essayer. Vous en apprendrez probablement beaucoup plus de cette manière sur les forces et les faiblesses d'un langage fonctionnel donné.

7
maxaposteriori

Une chose que je trouve très cool quand il s'agit d'algorithmes ou de problèmes mathématiques est l'évaluation paresseuse inhérente aux calculs de Haskell, qui n'est possible qu'en raison de sa nature fonctionnelle stricte.

Par exemple, si vous souhaitez calculer tous les nombres premiers, vous pouvez utiliser

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]

et le résultat est en fait une liste infinie. Mais Haskell l'évaluera de gauche à droite, donc tant que vous n'essayez pas de faire quelque chose qui nécessite la liste entière, vous pouvez toujours l'utiliser sans que le programme ne reste bloqué à l'infini, comme:

foo = sum $ takeWhile (<100) primes

qui somme tous les nombres premiers inférieurs à 100. C'est bien pour plusieurs raisons. Tout d'abord, je n'ai besoin que d'écrire une fonction principale qui génère tous les nombres premiers, puis je suis à peu près prêt à travailler avec des nombres premiers. Dans un langage de programmation orienté objet, j'aurais besoin d'un moyen de dire à la fonction combien de nombres premiers elle doit calculer avant de retourner, ou d'émuler le comportement de liste infinie avec un objet. Une autre chose est qu'en général, vous finissez par écrire du code qui exprime ce que vous voulez calculer et non dans quel ordre évaluer les choses - à la place, le compilateur le fait pour vous.

Ce n'est pas seulement utile pour des listes infinies, en fait, il est utilisé sans que vous le sachiez tout le temps, quand il n'est pas nécessaire d'évaluer plus que nécessaire.

7
waxwing

Je suis d'accord avec d'autres que voir quelques petits exemples n'est pas la meilleure façon de montrer Haskell. Mais je vais en donner quand même. Voici une solution ultra-rapide à problèmes 18 et 67 du projet Euler , qui vous demande de trouver le chemin de somme maximale de la base au sommet d'un triangle:

bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
  where bu [bottom]     = bottom
        bu (row : base) = merge row $ bu base
        merge [] [_] = []
        merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)

Voici une implémentation complète et réutilisable de l'algorithme BubbleSearch de Lesh et Mitzenmacher. Je l'ai utilisé pour emballer de gros fichiers multimédias pour le stockage d'archives sur DVD sans gaspillage:

data BubbleResult i o = BubbleResult { bestResult :: o
                                     , result :: o
                                     , leftoverRandoms :: [Double]
                                     }
bubbleSearch :: (Ord result) =>
                ([a] -> result) ->       -- greedy search algorithm
                Double ->                -- probability
                [a] ->                   -- list of items to be searched
                [Double] ->              -- list of random numbers
                [BubbleResult a result]  -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
    where bubble order rs = BubbleResult answer answer rs : walk tries
            where answer = search order
                  tries  = perturbations p order rs
                  walk ((order, rs) : rest) =
                      if result > answer then bubble order rs
                      else BubbleResult answer result rs : walk rest
                    where result = search order

perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
    where xr' = perturb xs rs
          perturb :: [a] -> [Double] -> ([a], [Double])
          perturb xs rs = shift_all p [] xs rs

shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
  where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
        shift_one new' xs rs k = shift new' [] xs rs
          where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
                shift new' prev' (x:xs) (r:rs) 
                    | r <= p    = k (x:new') (prev' `revApp` xs) rs
                    | otherwise = shift new' (x:prev') xs rs
                revApp xs ys = foldl (flip (:)) ys xs

Je suis sûr que ce code ressemble à du charabia aléatoire. Mais si vous lisez entrée du blog de Mitzenmacher et comprenez l'algorithme, vous serez étonné qu'il soit possible de compresser l'algorithme en code sans rien dire sur ce que vous recherchez.

Après vous avoir donné quelques exemples comme vous l'avez demandé, je dirai que la meilleure façon de commencer à apprécier Haskell est de lire l'article qui m'a donné les idées J'avais besoin d'écrire le pack de DVD: Pourquoi la programmation fonctionnelle est importante par John Hughes. Le document est en fait antérieur à Haskell, mais il explique avec brio certaines des idées qui font des gens comme Haskell.

6
Norman Ramsey

Pour moi, l'attraction de Haskell est la promesse de l'exactitude du compilateur garantie . Même si c'est pour des parties pures du code.

J'ai écrit beaucoup de code de simulation scientifique et je me suis demandé donc plusieurs fois s'il y avait un bug dans mes codes précédents, ce qui pourrait invalider beaucoup de travail en cours.

5
rpg

Je trouve que pour certaines tâches, je suis incroyablement productif avec Haskell.

La raison en est la syntaxe succincte et la facilité des tests.

Voici à quoi ressemble la syntaxe de déclaration de fonction:

foo a = a + 5

C'est la façon la plus simple de penser à définir une fonction.

Si j'écris l'inverse

inverseFoo a = a - 5

Je peux vérifier qu'il s'agit d'un inverse pour toute entrée aléatoire en écrivant

prop_IsInverse :: Double -> Bool
prop_IsInverse a = a == (inverseFoo $ foo a)

Et appeler depuis la ligne de commande

jonny @ ubuntu: runhaskell quickCheck + noms fooFileName.hs

Ce qui vérifiera que toutes les propriétés de mon fichier sont conservées, en testant au hasard cent fois les entrées.

Je ne pense pas que Haskell soit le langage parfait pour tout, mais quand il s'agit d'écrire de petites fonctions et de tester, je n'ai rien vu de mieux. Si votre programmation a une composante mathématique, c'est très important.

5
Jonathan Fischoff

Si vous pouvez faire le tour du système de typage à Haskell, je pense que c'est en soi tout un exploit.

3
Dr. Watson

il n'a pas de constructions en boucle. peu de langues ont ce trait.

2
Badri

Je suis d'accord avec ceux qui ont dit que la programmation fonctionnelle tord votre cerveau à voir la programmation sous un angle différent. Je ne l'ai utilisé que comme amateur, mais je pense que cela a fondamentalement changé la façon dont j'aborde un problème. Je ne pense pas que j'aurais été presque aussi efficace avec LINQ sans avoir été exposé à Haskell (et en utilisant des générateurs et des listes de compréhension en Python).

1
Jacob