Ghc, le Glorious Haskell Compiler est sorti le 9 avril 2014 en version 7.8.1. Il s'agit du compilateur le plus populaire pour le langage Haskell. Haskell est un langage de programmation purement fonctionnel, avec un système de types sophistiqué. C'est le principal langage généraliste à utiliser une stratégie d'évaluation non-stricte par défaut : la valeur d'une expression n'est calculée que si elle est nécessaire.
Sommaire
- Haskell en trois mots
- Les nouveautés de ghc 7.8.1
- Compilation vers javascript avec ghcjs
- Vers ghc 7.10
- Autres actualités de haskell
Haskell en trois mots
Haskell est un langage de programmation purement fonctionnel, avec un typage statique fort, et une stratégie d'évaluation non stricte.
Le Glasgow Haskell Compiler est le principal compilateur Haskell, bien qu'il en existe d'autres.
Quelques exemples
L'inévitable hello world :
main = putStrLn "Saluton Ĉirkaŭaĵojn!"
Suite de Fibonnacci :
fibs :: Int -> Int
fibs 0 = 0
fibs 1 = 1
fibs n = fibs (n-1) + fibs (n-2)
-- Sous forme de liste infinie
fibs':: [Int]
fibs' = 0:1:(zipWith (+) fibs' (tail fibs'))
Un langage fonctionnel pur
La spécificité la plus marquante de Haskell, c'est qu'il s'agit d'un langage fonctionnel pur : une fonction appelée avec les mêmes arguments renverra toujours le même résultat, contrairement à une fonction comme input(message)
en python.
Cela rend possible la transparence référentielle : une expression peut être remplacée par son résultat sans changer le comportement de l'application. Le raisonnement sur les équations est mené par substitution sans inquiétude des effets de bord.
Les entrée-sorties et autres effets de bord sont bien sûr possibles, mais ils sont distingués dans le système de type des calculs sans effets de bord. Cela permet des optimisations assez poussées et garanties correctes sur ces derniers. Cette distinction des effets de bord dans le système de type est déroutante au début, mais avec l'expérience, elle apporte un réel confort.
Avec un typage puissant
Le typage statique permet de vérifier à la compilation la cohérence d'un programme et de détecter de nombreuses classes d'erreurs fréquentes. Un type statique est une approximation réalisée pendant la compilation des valeurs rencontrées au cours de l'exécution. Il est extrêmement difficile de concevoir un système de type à la fois flexible (faiblement verbeux, avec une inférence utile), complet (de grain fin, éliminant plus de bugs) et correct (pas de programmes justes rejetés). Haskell essaye de trouver le juste milieu pour que la programmation généraliste soit efficace.
Les aficionados du typage dynamique et du « duck typing » argumentent que, lors du développement avec les langages statiquement typés (typiquement C/C++), une grande partie des ressources sont gaspillées à combattre le système de type. Le typage statique de Haskell est rendu extrêmement flexible grâce au polymorphisme paramétrique, similaire aux templates en C++, mais avec des possibilités d'inférence bien plus développées.
Un type polymorphique peut être restreint à l'aide de contraintes, c'est-à-dire un ensemble de spécifications (ou typeclasses). Par exemple pour insérer un élément dans un Set
, il est nécessaire de pouvoir le comparer aux éléments déjà présents avec la méthode compare
de la typeclasse Ord
, d'où la contrainte Ord a =>
dans le type de insert
:
insert :: Ord a => a -> Set a -> Set a
En pratique, dans les cas courants, la seule différence induite par le système de types entre un code haskell et le code python correspondant est la présence (facultative mais habituelle) en haskell de la signature de chaque fonction… Signatures qui sont en cours d'adoption par la communauté python.
Les nouveautés de ghc 7.8.1
Changements du langage
Typed holes
Grâce au typage fort de haskell, on se trouve souvent à utiliser les types pour guider la conception : on écrit d'abord les types dont on va avoir besoin, puis les signatures des fonctions. Ensuite, de proche en proche, l'implémentation des fonctions est guidée par les signatures. Bien sûr, dans les cas les plus complexes, il faut d'autres ressources que cette méthode, mais on y devient rapidement accro. L'extension typed holes (littéralement « trous typés ») permet d'utiliser cette démarche dans le corps d'une fonction.
Cette extension est souvent utilisée conjointement avec le drapeau -fdefer-type-errors qui transforme les erreurs de typage en warnings. Dans ghci, cela permet de charger toutes les définitions et de les manipuler même si leurs types sont incorrects. Une expression mal typée ou contenant des typed holes est équivalant à undefined et déclenchera une erreur uniquement si elle est évaluée.
En pratique, un hole est un _
placé dans le code à la place d'une expression manquante (ici à la dernière ligne):
getSensorReading :: IO Double
getSensorReading = return 123.45
-- TODO: utiliser vraiment la sonde thermique
main = do
tKelvins <- getSensorReading
putStrLn ("Aujourd'hui il fait " ++ _ ( tKelvins - 273.15) ++ " degrés")
À la compilation, ghc émet une erreur (ou un warning si -fdefer-type-errors est activé) pour chaque hole, et affiche son type:
Found hole ‘_’ with type: Double -> [Char]
Relevant bindings include
tKelvins :: Double (bound at toto.hs:6:5)
main :: IO () (bound at toto.hs:5:1)
In the expression: _
In the first argument of ‘(++)’, namely ‘_ (tKelvins - 273.15)’
In the second argument of ‘(++)’, namely
‘_ (tKelvins - 273.15) ++ " degrés"’
Dans notre exemple, le warning à la compilation nous indique que pour afficher la température dans le putStrLn
, il nous faudra une fonction de type Double -> String
. Une simple recherche dans hoogle nous indique que la fonction en question n'est autre que show
.
OverloadedLists
Haskell repose sur une syntaxe très concise, la majeure partie du langage (Opérateurs, type de base, etc) étant définie dans le Prelude.
En revanche, ce n'est pas le cas des expressions littérales comme les nombres, les chaines de caractères et les listes. Par exemple [1, 2, 3]
est interprété par le compilateur comme 1:2:3:[]
, où (:)
et []
sont les constructeurs du type liste défini dans le Prelude. OverloadedStrings autorisait déjà les chaînes de caractères littérales avec d'autres types que [Char]
, notamment le type Text
pour l'encodage UTF-8.
Cette extension OverloadedLists est documentée, elle permet d'utiliser la syntaxe des littéraux listes avec d'autres types, comme par exemple les vecteurs, les chaînes du module Text
ou encore les ensembles.
Pour utiliser cette extension, il faut disposer d'une instance de la classe IsList
. Il faudra donc attendre que ces instances soient intégrées aux bibliothèques haskell pour vraiment en profiter.
PatternSynonyms
Une autre extension, PatternSynonyms permet de donner un nom à un motif pour le filtrage.
La puissance des types de donnée algébrique (ADT) repose sur la possibilité de filtrer les données par motif, comme en Scala :
type Errno = Int
data Error = ErrorLibC Errno
| ErrorIO IOException
errorToStr :: Error -> String
errorToStr (ErrorIO ioe) = "Haskell's IOException: " ++ show ioe
errorToStr (ErrorLibC errno) = "LibC's errno: " ++ show errno
Chaque constructeur du type Error
défini un motif, utilisé par la fonction errorToStr
pour filtrer les erreurs. En revanche il n'était pas possible de définir ses propres motifs en plus des constructeurs. PatternSynonyms permet maintenant cela, par exemple pour avoir plusieurs motifs par constructeurs :
pattern ErrorPERM = ErrorLibC 1
pattern ErrorNOENT = ErrorLibC 2
pattern ErrorSRCH = ErrorLibC 3
errorToStr :: Error -> String
errorToStr (ErrorIO ioe) = "Haskell's IOException: " ++ show ioe
errorToStr ErrorPERM = "Operation not permitted"
errorToStr ErrorNOENT = "No such file or directory"
errorToStr ErrorSRCH = "No such process"
errorToStr (ErrorLibC errno) = "LibC's unknown errno: " ++ show errno
Les motifs synonymes sont aussi utilisés pour cibler des données profondément imbriquées : pattern Greetings name = 'h':'e':'l':'l':'o':' ':name
. Par défaut il sont bidirectionnels : Greetings "world"
est une valeur. Il est possible de définir des synonymes unidirectionnels lorsqu'il n'existe pas de bijection.
TypeFamilies fermées
Il est possible de déclarer une famille de type et ses instances dans la même clause, par exemple:
type family TF a where
TF ([Bool]) = Int
TF ([Bool],[Bool]) = Double
La famille est alors fermée, c'est-à-dire qu'il n'est pas possible de déclarer de nouvelles instances de TF
.
Améliorations du compilateur
De nombreuses améliorations portent sur le compilateur lui-même, sans modifier le langage. Il est toujours plus rapide et économe en mémoire, notamment pour l'inférence de types. La compilation en parallèle de modules est activable avec la nouvelle option -jN
.
Sur Linux, FreeBSD et Mac OS X, GHCi
utilise maintenant le chargeur de code dynamique du système. Auparavant, un chargeur maison liait dynamiquement du code statique (.a
) en mémoire. Pour utiliser cette fonctionnalité, il est nécessaire de compiler avec -dynamic-too
pour générer des bibliothèques dynamiques (.dyn.so
).
Améliorations de la génération de code
La qualité du code que ghc
produit s'améliore aussi :
-
ghc
est capable d'utiliser plus de types «nus» (unboxed), avec l'activation de-funbox-small-strict-fields
par défaut; - le gestionnaire d'entrée sortie se parallélise mieux, «de façon linéaire jusqu'à 32 cœurs»;
- le nouveau générateur de représentation intermédiaire Cmm a été activé, après une gestation de plusieurs années;
- de nouvelles primitives (PrimOps) pour les instructions SIMD sont disponibles, pour l'instant uniquement avec le générateur LLVM.
Amélioration du gestionnaire d'entrée-sorties
Le gestionnaire d'entrée-sorties était un des points les plus critiques de la performance du runtime haskell. Dans la version 7.8.1, il a été grandement amélioré, en particulier dans un contexte multi-processeurs. Haskell devient ainsi de plus en plus crédible comme meilleur langage impératif du monde.
Compilation vers iOS
La gestion de la compilation croisée a été améliorée, ce qui permet notamment de compiler vers les systèmes iOS.
Compilation vers javascript avec ghcjs
La sortie de la version 7.8 devrait être accompagnée de la première version publique de ghcjs
, un compilateur haskell vers javascript. Ce langage devient de plus en plus l'assembleur du client web et ghc prend ainsi en charge cette plateforme. Par rapport à Fay, qui est plus ancien, ghcjs cherche à compiler tout le langage haskell et pas seulement un sous-ensemble.
L'introduction à ghcjs permet de se faire une bonne idée de la façon de l'utiliser, en particulier pour les parties spécifiques à javascript.
Vers ghc 7.10
La version 7.10.1 sera la prochaine version majeure de ghc. On trouve sur le trac de ghc une liste de choses qui devraient figurer dans cette prochaine version. Certaines nouveautés de cette version sont préparées dès la 7.8.
Monad / Applicative
Ces deux concepts permettent d'associer un effet à une valeur : IO Int
peut par exemple représenter l'action de la lecture d'un entier présent dans un fichier. La notion de foncteur applicatif est plus générale : toutes les monades sont des foncteurs applicatifs, tandis que l'inverse n'est pas nécessairement vrai. L'exécution des effets d'une monade peut dépendre des valeurs obtenues des effets précédents, contrairement aux foncteurs applicatifs où les effets sont exécutés dans l'ordre des arguments.
La classe de type Monad fut introduite dans les premières versions du langage pour gérer les entrées/sorties avec la monade IO
. Lorsque, plus tard, la classe de type Applicative apparût dans la bibliothèque de base, Monad n'a pas été modifiée pour avoir Applicative comme super-classe.
Le but de la proposition Functor < Applicative < Monad est de corriger ce problème pour réduire la redondance (par exemple entre return
et pure
, liftM
et fmap
).
Ce changement, susceptible de rendre du code incompatible, est déjà signalé par un warning dans GHC 7.8 et sera officialisé dans la version 7.10.
Autres actualités de haskell
Olivier Charles a publié sur son blog l'édition 2013 de sa chronique 24 Days of Hackage qui offre une série d'introductions (avec des exemples) aux paquets majeurs disponibles sur Hackage. L'édition 2012 vaut également le détour, car elle couvre des paquets encore plus incontournables (base, containers, transformers, etc.).
Hackage 2
Le site hackage qui répertorie les paquets haskell libres, exécutables et bibliothèques est passé en version 2 en septembre 2013. L'aspect visuel a été amélioré, la recherche concerne maintenant aussi la description des paquets. La plupart des informations sont plus accessibles, en consultation mais aussi à la modification : le nouveau hackage est plus permissif, et il a une API rest plus étendue.
D'autres fonctionnalités ont été développées mais pas encore déployées, comme la gestion flexible des tags et le calcul des dépendances inverses.
Haskell platform
Il n'est généralement pas recommandé d'installer ghc seul, mais plutôt de récupérer la plateforme haskell, qui contient ghc ainsi que les bibliothèques les plus essentielles du monde haskell. Comme la version 7.8 de ghc a tardé, la plateforme n'a pas été mise à jour depuis la version 2013.2 de mai 2013. Une nouvelle version, adaptée à ghc 7.8 devrait sortir prochainement.
Aller plus loin
- Tout sur Haskell (144 clics)
- « Apprendre Haskell vous fera le plus grand bien ! », une introduction à Haskell (348 clics)
- Les changements de la version 7.8.1 (53 clics)
# Typos et signatures en python
Posté par Remus . Évalué à 1.
Dans ghci, cela permet de charger toutes les définitions
ce n'est pas le cas des expressions littérales comme les nombres
Faut-il un 's' ici ?
La famille est alors fermée, c'est-à-dire
Monad n'a pas été modifiée
à la modification_:
Fais-tu référence à Cython ? (sinon ça m'intéresse de savoir plus précisément !)
Merci pour cet article, c'est très intéressant !
[^] # Re: Typos et signatures en python
Posté par dowst . Évalué à 2.
Je suppose qu'il fait référence à http://legacy.python.org/dev/peps/pep-0362/ pour l'histoire des signatures en python. Merci également pour cette article effectivement très intéressant !
[^] # Re: Typos et signatures en python
Posté par Remus . Évalué à 1.
D'accord merci, mais j'ai du mal à voir le lien entre les signatures Haskell et ce qui est proposé dans la PEP 362. Il ne me semble pas avoir vu la possibilité d'annoter les signatures avec les types de chaque argument (ce qui ne serait pas forcément très pythonic si ?). J'ai plutôt compris qu'il s'agissait d'une manière plus simple d'accéder à la "signature" d'une fonction via le module inspect, pas vraiment d'une adoption des "signatures".
[^] # Re: Typos et signatures en python
Posté par galbolle . Évalué à 1.
Je pensais plus à la 3107, mais elle est dans le même esprit. Les annotations de cette PEP sont plus générales que des types, mais il me semble que les types sont l'usage le plus immédiat.
[^] # Re: Typos et signatures en python
Posté par Bruno Michel (site web personnel) . Évalué à 2.
Merci, les typos sont corrigées.
# Fibo
Posté par Anthony Jaguenaud . Évalué à 1.
Quand j'ai lu :
je me suis dit : chouette, je vais essayer fib 100. Mais, je m'attendais à ce qu'il stocke dans un cache une partie du résultat des fonctions en interne pour gagner du temps puisque avec des paramètres donnés, il n'y a qu'un résultat. Apparemment non, en tous cas pas par défaut.
Du coup j'ai essayé en template C++ pour le faire compiler par le compilateur. C'est efficace :
[^] # Re: Fibo
Posté par Dr BG . Évalué à 8.
Il faut le demander explicitement : http://www.haskell.org/haskellwiki/Memoization
[^] # Re: Fibo
Posté par galbolle . Évalué à 7.
Effectivement, il n'y a pas de « cache des résultats » (mémoïsation dans les termes de l'art), parce que savoir si c'est une bonne idée d'en faire un pour une fonction donnée est indécidable. Cependant, la version liste infinie qui est donnée est mémoïsée par le simple fait de produire une structure de données entière:
En plus, elle n'oblige pas à fixer 1000 à la compilation.
[^] # Re: Fibo
Posté par Anthony Jaguenaud . Évalué à 2.
Merci, je ne connaissais pas le terme. La solution avec la liste, j'y avais pensé, mais j'avais fait une implémentation naïve. Du genre :
qui ne m'a demandé que très peu de temps, malgré le fait que je ne connaisse pas le langage.
Du fait que le langage soit purement fonctionnel, je m'attendais naïvement à ce que le runtime gère une zone mémoire de cache automatiquement ou avec une option de compilation.
Le C++ était juste un exemple de template, le compilateur stocke toute les valeurs intermédiaires, et le linkeur ne retient que celle dont il a besoin. Je trouvais ça intéressant. Mais c'est évident que faire une version avec cache automatique en c++ demande également plus qu'une simple implémentation naïve. Ou alors en itératif.
En tous cas, la présentation ici donne envie d'essayer plus avant ce langage.
[^] # Re: Fibo
Posté par galbolle . Évalué à 3.
Tu trouveras beaucoup plus de références avec l'anglais memoization.
[^] # Re: Fibo
Posté par neil . Évalué à 3. Dernière modification le 16 avril 2014 à 02:07.
Ou, plus sobrement :
On peut vérifier que ça mémoïse bien les résultats dans ghci :
[^] # Re: Fibo
Posté par galbolle . Évalué à 10.
C'est plus sobre certes, mais ça donne les factorielles plutôt que la suite de fibonacci.
[^] # Re: Fibo
Posté par hsyl20 (site web personnel) . Évalué à 10.
Le problème avec la mémoïsation automatique est de déterminer quand la faire. Il y a un compromis à trouver entre la vitesse d'exécution et l'espace mémoire (comme d'hab), pour quelles fonctions, selon quels paramètres en entrée, etc.
La version avec l'utilisation d'une liste infinie doit normalement éviter de calculer plusieurs fois le même élement et le garbage collector se charge de supprimer les éléments au début de la liste qui ne sont plus nécessaires.
Pour ton exemple C++, outre le fait qu'il manque des "::result", tu as conscience que c'est exécuté à la compilation et donc je suis moyennement surpris qu'afficher une constante soit efficace à l'exécution.
Après l'exemple de la suite de Fibonacci n'est pas terrible puisque personne ne se sert d'Haskell pour calculer ça. Il y a tellement de choses plus intéressantes. Quelques exemples :
1) Happstack (ou Yesod) pour intégrer un serveur web en quelques lignes dans une appli. L'analyse de l'url est type-safe, avec les modules de génération de HTML (comme blaze-html) on génère forcément des pages well-formed (vérifié statiquement par le système de types), en étant sûrs de ne renvoyer de façon non intentionnelle de chaînes de caractères qui ne soient pas converties en HTML ("échappées"), etc.
2) Intégration de la mémoire transactionnelle. Pas besoin de se prendre la tête à gérer des mutexes ou autres. L'exemple classique du transfert d'argent entre 2 comptes :
Le gros avantage c'est que c'est composable (pas besoin de prendre de mutexes dans l'ordre, etc.).
3) QuickCheck pour tester automatiquement des fonctions en fonction de leurs types. Par exemple si vous écrivez une fonction de tri de caractères et que vous vouliez voir si elle a l'air conforme à un tri classique :
Je vous invite à découvrir Haskell si ça n'est pas déjà fait. L'apprentissage est peut-être un peu difficile mais ça en vaut la peine.
[^] # Re: Fibo
Posté par Anthony Jaguenaud . Évalué à 3.
oups.
Oui, c'est le temps de compilation qui aurait pu exploser. Ce n'est pas le cas car au fur et à mesure, les constantes ont leur valeur. A la fin, il ne garde que celle dont il a besoin.
Pour le vous, je suis vieux, mais ce n'est pas la peine de me le rappeler ;-)
J'ai bien compris le principe de
mémoire transactionnelle
, mais pas la syntaxe de l'exemple.[^] # Re: Fibo
Posté par hsyl20 (site web personnel) . Évalué à 7.
J'aurais du écrire "j'invite tout le monde" au lieu de "je vous invite", ce n'était pas du vouvoiement ;)
Pour la syntaxe de l'exemple utilisant la mémoire transactionnelle, c'est la façon dont on écrit du code "impératif" en Haskell avec la do-notation. Donc ça se lit comme on lirait du C ou autre :
Pour montrer comment on peut composer des fonctions qui utilisent la mémoire transactionnelle, on peut introduire deux fonctions
retirer
etverser
pour enlever ou ajouter de l'argent à un compte donné :[^] # Re: Fibo
Posté par ckyl . Évalué à 6. Dernière modification le 15 avril 2014 à 09:37.
C'est niveau CS 101 mais tu devrais l'envoyer aux Jean Kevin de la nouvelle finance mondiale: Les exchanges Bitcoins. Ca fait au moins deux qui ferment et se font piller sur exactement cet exemple, et pleins d'autres ont eu le soucis.
Quand tu te dis que Bitcoin remet en cause tous les processus de consistance, de sécurité et d'exposition aux risque des banques/exchange classiques par ce l'eventual consistancy ça ne marche pas des masses sur des transaction anonymes. Et quand tu vois le niveau des gars qui arrivent à se vautrer sur une bête balance de compte… :P
[^] # Re: Fibo
Posté par hsyl20 (site web personnel) . Évalué à 6.
Ouch, effectivement ça craint.
Pour leur défense, c'est vrai que les langages de haut-niveau ne sont pas vraiment mis en avant dans les cursus informatique (en tout cas le mien). On dégoutte les élèves en leur faisant faire de la programmation impérative en LISP et en leur disant que c'est ça la programmation fonctionnelle, on leur vend Java comme étant le top du haut-niveau et ils sont mûrs pour la SSII.
Donc ne parlons même pas de mémoire transactionnelle, de modèle data-flow, de modèle d'acteur, etc.
[^] # Re: Fibo
Posté par ariasuni . Évalué à 5.
Les échanges Bitcoin/de Bitcoins.
Les processus de cohérence. (consistance veut dire tout à faire autre chose en français)
exposition aux risques des banques/échanges classiques parce que la «cohérence finale» ça ne marche pas des masses sur des transactions anonymes.
Écrit en Bépo selon l’orthographe de 1990
[^] # Re: Fibo
Posté par navaati . Évalué à 2.
Non. Là il parle bien des exchanges (tu veux de la typographie ? En italique, j'ai le droit de mettre des mots étrangers :p ), les places de marché comme MtGox.
[^] # Re: Fibo
Posté par ariasuni . Évalué à 2.
Une bourse alors?
Écrit en Bépo selon l’orthographe de 1990
[^] # Re: Fibo
Posté par ckyl . Évalué à 3. Dernière modification le 16 avril 2014 à 00:11.
C'est bien tu as compris tous les mots. Tu es maintenant prêt pour créer ton exchange Bitcoin, cours chercher ton MongoDB et révolutionne l'histoire des catastrophes financières ;)
[^] # Re: Fibo
Posté par neil . Évalué à 6. Dernière modification le 15 avril 2014 à 20:51.
À noter (pour rebondir sur la deuxième partie du commentaire, le code, qui n’a presque rien à voir avec la première partie, la mémoïsation) qu’on peut aussi faire de la meta-programmation grâce à l’extension Template Haskell. Une extension très utilisée, qui permet aussi de faire de la meta-programmation à l’exécution (!) et qui voit sa sûreté renforcée grâce au typage dans cette nouvelle version. Probablement l’une des avancées fondamentales de GHC 7.8.
[^] # Re: Fibo
Posté par Michaël (site web personnel) . Évalué à 2.
Oui mais ça n'a rien à voir avec l'évaluation de la fonction pendant l'éxécution du programme.
Tu peux aussi directement écrire la valeur à la main, ou utiliser un tableau avec les premières valeurs.
# La 7.8.2 est déjà dans les bacs...
Posté par Frédéric Blanc . Évalué à 5.
et vivement recommandée puisque contenant le correctif pour une régression du vérificateur de type (bug 8978).
# Bizarre, aucun commentaire ou comparaison avec Ocaml.
Posté par Serge Leblanc . Évalué à 2. Dernière modification le 15 avril 2014 à 16:28.
Saluton samideanoj! Vraiment surprenant de ne voir jusqu'à présent aucun commentaire comparant Haskell et Ocaml ?
[^] # Re: Bizarre, aucun commentaire ou comparaison avec Ocaml.
Posté par Michaël (site web personnel) . Évalué à 2.
Je trouve cela plutôt curieux de citer Scala plutôt que OCaml pour illustrer le filtrage par exemple.
[^] # Re: Bizarre, aucun commentaire ou comparaison avec Ocaml.
Posté par hsyl20 (site web personnel) . Évalué à 2.
Peut-être parce que Scala permet de définir des "extracteurs" particuliers qui peuvent être utilisés lors du pattern-matching : http://www.scala-lang.org/old/node/112
Ça ressemble un peu aux Pattern Synonyms introduits dans GHC 7.8. Je ne connais pas assez Ocaml pour savoir s'il dispose de quelque chose d'équivalent.
# cat troll.hs
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à 0.
J'ai essayé avec le code suivant :
Mais à chaque fois que j'appelle la fonction foo avec le paramètre "3", j'obtiens un résultat différent. Mon GHC est-il cassé ?
[^] # Re: cat troll.hs
Posté par neil . Évalué à 6.
Si le langage était vraiment pur, tu n’aurais ni affichage pour voir le résultat, ni rien du tout, le simple fait d’exécuter du code sur un ordinateur a un effet (par exemple en cherchant à allouer une grande quantité de mémoire dans un système plus ou moins chargé).
En Haskell, le code est par défaut pur, et le code impur est très visiblement marqué comme tel. Dans ton exemple,
foo
est de typeNum a => a -> IO a
. On travaille donc dans la monadeIO
, qui a été spécialement conçu pour gérer les effets de bords. Mais il est vrai que la formulation de la dépêche est quelque peu obscure. L’équivalent Haskell d’une entrée commeinput
en Python est forcément impur.D’un point de vue moins pragmatique, tu te trompes. Ta fonction
foo
n’a pas renvoyé un résultat différent à chaque fois. Elle a toujours renvoyé uneNum a => IO a
. Le fait qu’a un moment donné quelque chose (une valeur numérique, probablement unInt
) ait été généré à partir de cetteIO
n’affecte pas la pureté defoo
.[^] # Re: cat troll.hs
Posté par hsyl20 (site web personnel) . Évalué à 10.
Oui mais justement le gros intérêt de Haskell en comparaison avec d'autres langages comme OCaml par exemple, c'est qu'ils n'ont pas transigé sur la pureté pour intégrer les IOs. Donc on conserve toutes les propriétés liées à la pureté.
(Brièvement) pour ceux qui se demandent comment ça marche (j'avais présenté ça ici) :
Si on avait une fonction
input
qui renvoyait une chaîne en faisant un effet de bord (lecture de ce que tape l'utilisateur) et ayant pour typeinput :: String
. Supposons qu'on fasse plusieurs lectures :Comme Haskell est paresseux,
prenom
etnom
seraient évalués seulement quand on aurait besoin d'afficher la chaîne (donc dans quel ordre ?) etage
ne serait jamais évalué. Comme il y a la transparence référentielle et qu'on appelleinput
avec les mêmes paramètres (i.e. aucun), on peut dire quenom = prenom = age = input
. Bref ça ne marche pas, d'autant plus que commeprint
ne renvoie rien, on n'a jamais besoin de l'évaluer donc il ne se passerait rien.On s'en sort en disant que
input
prend en fait un paramètre, l'état du monde extérieur à Haskell et qu'en plus d'une chaîne il renvoie l'état du monde extérieur après avoir fait l'IO. Donc son type est en faitinput :: RealWorld -> (RealWorld,String)
. De la même façon, on suppose quemain
reçoit l'état du monde extérieur avant le lancement du programme et renvoie l'état du monde extérieur après l'exécution. Maintenant il ne reste plus qu'à chaîner tout ça :Donc là on est sûr que pour obtenir l'état du monde renvoyé par
main
, il faut évaluerprint... w4
, donc pour obtenirw4
il faut évaluerinput w3
et ainsi de suite. Notez qu'on aurait pu faire autrement et dire que chaque fonction qui effectue une IO prend en paramètre une fonction qui va utiliser son résultat (continuation passing style, CPS) et on aurait obtenu Node.js, mais Haskell est un langage moderne qui a remplacé cette approche en 1992 (lancer de troll réussi ?).On peut introduire un alias pour simplifier l'écriture des types :
Donc on aurait
input :: IO String
,print :: String -> IO ()
(où()
veut direvoid
) etmain :: IO ()
.Le problème avec les variables
world
explicites est que si le programmeur fait n'importe quoi avec (s'il en utilise une plusieurs fois, etc.) ça n'est plus déterministe et c'est n'importe quoi. Donc soit on détecte statiquement que l'utilisateur fait n'importe quoi (ce qu'ils ont fait dans le langage Clean plus tard), soit on cache l'utilisation de ces variables en ne proposant que l'opérateur pour composer deux opérations qui font des IO (l'équivalent de ";" en C). Donc en Haskell c'est ça, on peut utiliser>>=
pour composer deux opérations, avec(>>=) :: IO a -> (a -> IO b) -> IO b
:Et comme c'est très moche, on utilise l'équivalent avec du sucre syntaxique (do-notation) :
Le principe et la notation se généralisent à d'autres cas où on cherche à "chaîner des opérations" (cf Monad), c'est pour ça qu'on la retrouve dans l'exemple utilisant la mémoire transactionnelle dans mon commentaire plus haut alors qu'il n'y a pas d'IOs.
Bref au final c'est resté pur tout en faisant des IOs, c'est pour ça que certains parlent de "meilleur langage impératif du monde" comme le mentionne la dépêche. J'aurais dit "univers" mais bon. ;)
[^] # Re: cat troll.hs
Posté par Michaël (site web personnel) . Évalué à 4.
Merci pour ces explications, très claires.
En anglais j'aime bien le texte suivant comme introduction aux monades:
http://blog.enfranchisedmind.com/2007/08/a-monad-tutorial-for-ocaml/
C'est très court et on comprend assez bien diverses applications de ce design pattern ou plutôt de ce patron applicatif.
Question: que signifie cette notation
\nom
?[^] # Re: cat troll.hs
Posté par rogo . Évalué à 3.
\
représente le caractère λ, en référence au λ-calcul et à ses fonctions anonymes. Donc\nom -> "Salut " ++ nom
est équivalent à\x -> "Salut " ++ x
.Il existe une extension livrée avec GHC qui permet de compiler
λnom → "Salut " ⧺ nom
. On peut aussi configurer vim et emacs pour qu'ils affichent le code sous cette forme, tout en transmettant la version ASCII au compilateur.[^] # Re: cat troll.hs
Posté par Michaël (site web personnel) . Évalué à 3.
Cela permet donc d'introduire une fonction c'est ça?
Traducation OCaml
fun nom -> "Salut " ^ nom
.Traduction LISP
(lambda (nom) (concat "Salut " nom))
.[^] # Re: cat troll.hs
Posté par hsyl20 (site web personnel) . Évalué à 3.
Oui c'est bien ça.
[^] # Re: cat troll.hs
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à -1.
La fonction n'est pas pure, car son résultat dépend d'un état caché, celui de la monade
IO
. Plus précisément, elle dépend derandomIO
qui n'est pas pure et dont elle extrait une valeur qu'elle retourne. On en a pour preuve que le résultat est effectivement différent.Tu parles de toujours retourner
Num a => IO a
, mais c'est le type, non la valeur. Le type est habité par un grand nombre de valeurs, plusieurs desquelles peuvent être retournées par des appels successifs àfoo
, ce qui s'observe en pratique.(NB: on peut tout à fait avoir une fonction pure dans IO:
Cette fonction retournera toujours la même valeur, et n'effectue aucun effet de bord observable, en dépit du fait qu'elle est "marquée".)
[^] # Re: cat troll.hs
Posté par neil . Évalué à 4. Dernière modification le 16 avril 2014 à 01:47.
Le post de Sylvain Henry explique bien pourquoi justement cette IO n’a pas de type caché et est bien pure. La fonction
randomIO
retourne uneIO
qui n’est pas la valeur du nombre aléatoire, mais une monade qui indique comment obtenir une valeur. Mais ce comment reste toujours le même quelque soit le nombre de fois que tu appellesfoo
ourandomIO
.On peut voir ce comment en action en n’appellant qu’une seule fois
randomIO
, afin d’obtenir une seule valeur de retour (de typeIO double
par exemple), et en l’invoquant plusieurs fois.On peut faire de même avec ta fonction
foo
, ne l’appeller qu’une seule fois, et se servir de la monade retournée pour obtenir plusieurs valeurs.IO
ne fournit qu’une méthode pour accéder au monde, mais ne modifie pas le monde en elle même.Le tout est expliqué et formalisé par l’auteur du langage, Simon Peyton Jones, dans son célèbre Tackling the awkward squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell.
[^] # Re: cat troll.hs
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à -3.
randomIO
ne retourne pas une monade (les monades ne sont pas des citoyens de première classe en Haskell).randomIO
, fonction zéro-adique, retourne une valeur dans la monade IO. Et c'est différent.Quand tu fais
f = randomIO
tu n'évalues pasrandomIO
, tu crées un nouveau binding vers la fonctionrandomIO
(en gros, un alias). En évaluant ta variablef
plusieurs fois, tu appelles la fonction zéro-adiquerandomIO
autant de fois, et tu obtiens donc des résultats différents (résultats qui sont extraits par<-
et non par=
).Ce comportement se comprend mieux si on compare à la monade
State
qui fonctionne de manière similaire, mais à laquelle tu fournies l'objet de typeWorld
explicitement. Tu peux écrire une fonctionrandomState
qui se comporte de façon similaire. Cette fonction serait pure. Mais avec IO, tu es incapable de reproduire l'exécution, car tu es incapable de fournir ou extraire le monde (contrairement à la monadeState
). En pratique, c'est donc impure. C'est important, car on programme plus en pratique qu'en théorie.[^] # Re: cat troll.hs
Posté par neil . Évalué à 2.
WTF ? Et surtout quelle est la suite ? Les nombres n’existent pas en Haskell ? Les listes non plus ? La syntaxe
do
non plus ?Non. Ce concept n’existe pas en Haskell.
Ce n’est pas une fonction, c’est une valeur de type
Num a => IO a
(cf le tuto de SPJ, et les autres commentaires).(Et là, je suis sûr d’avoir marché dans le troll pourtant bien annoncé dans le sujet du thread.)
[^] # Re: cat troll.hs
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à -2.
Tu peux faire
f :: m a -> int
mais pas "g :: m -> b" oùm
est une monade, ni "h :: m a -> m".Pour moi, ça fait que les monades ne sont pas citoyens de première classe.
[^] # Re: cat troll.hs
Posté par neil . Évalué à 4. Dernière modification le 01 mai 2014 à 18:17.
C’est parce qu’on ne travaille pas dans un système de types simpliste, comme le lambda calcul simplement typé. Il y a plusieurs kinds, qui sont des constructeurs de types (juste deux en Haskell, ceux d’arité 0 ou 1, les autres sont obtenus par curryfication). Ici tes
Int
etb
sont de kind*
, maism
est de kind* -> *
, j’imagine. Plus d’infos dans le classique TAPL de Pierce,Pour simplifier, c’est pareil qu’en C, tu ne peux pas écrire qu’une fonction prend en paramètre un tableau, sans spécifier le type de ses éléments (pas de
func (int x, [] y)
). En Haskell, tu peux quand même généraliser avecforall a. func :: Int -> Monad a
par exemple. (En C, on doit outrepasser le système de types par des casts).[^] # Re: cat troll.hs
Posté par Piezo . Évalué à 2.
Une valeur de type
IO a
n'est pas une valeur «marquée impure», mais une action non pure produisant une valeur de typea
. En théorie,IO
n'est pas un conteneur contenant une valeur de typea
. La notion de foncteur, et par extension de monade, est plus générale que celle de conteneur.C'est seulement lorsque cette action est exécutée dans le
main
ou l'invite de ghci que la valeur aléatoire est générée. Dans le reste du programme, il n'y a que des fonctions pures qui combinent des actions monadiques avec>>=
pour assembler l'action non pureIO ()
du main.En pratique, (mais cela importe peu) cela est implémentée avec un passage de bâton
RealWorld
, comme l'a montré Sylvain Henry. Il est également possible d’altérer cette mécanique cachée avec des fonctions commeunsafePerformIO :: IO a -> a
. Mais comme son nom l'indique, elle peut potentiellement supprimer les garanties de la transparence référentielle.# Applications
Posté par Michaël (site web personnel) . Évalué à 3.
Une critique récurrente de Haskell — surtout en comparaison à OCaml — est le manque d'applications industrielles effectives.
Pour OCaml, celles-ci sont anciennes (Microsoft, Dassault, Intel) toujours plus nombreuses mais quid de Haskell? Haskell est utilisé en interne par le crédit suisse (en tout cas ils recrutent des programmeurs haskell) pour faire du pricing et du risk management. Mais y-a-t-il un catalogue plus étoffé d'utilisateurs industriels?
Mes amis haskelleurs disent qu'un gros défaut du langage est la difficulté à optimiser et profiler le programme. (Si je comprends bien, comme l'évaluation n'est pas stricte, il n'y aurait pas de correspondance claire entre le code source du programme et le flot des traitements effectués par le programme.) Existe-t-il des méthodes efficaces pour cela? En regard de Haskell, OCaml est très simple et on suit très facilement son code source original en lisant le code assembleur généré.
[^] # Re: Applications
Posté par zul (site web personnel) . Évalué à 3.
Ils ont même fait une page wiki pour répondre à ta questions
http://www.haskell.org/haskellwiki/Haskell_in_industry
[^] # Re: Applications
Posté par rogo . Évalué à 5.
Optimiser est toujours difficile, quel que soit le langage. Avec GHC, profiler un code m'a paru plutôt simple12. En dehors des expérimentations-jouets, j'avais obtenu un (très utile) profilage par fonction pour une appli web réalisée avec Yesod.
Si tu trouves que lire l'assembleur d'OCaml est très facile, alors GHC devrait te plaire : à partir du code Haskell, il peut générer un code fonctionnel bas niveau en "Core"3 ou un code en C. Un thème central pour l'optimisation est le choix de la structure de données (et de la bibliothèque qui l'implémente), mais tout cela me semble bien documenté4.
Une introduction simple au profiling GHC, ancienne mais encore d'actualité : http://stackoverflow.com/questions/3276240/tools-for-analyzing-performance-of-a-haskell-program/3276557#3276557 ↩
http://www.haskell.org/haskellwiki/Profiling_walkthrough ↩
Conseils et exemples d'optimisation, notamment en Core : http://www.haskell.org/haskellwiki/Performance/GHC ↩
http://www.haskell.org/haskellwiki/Performance ↩
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.