URL: https://linuxfr.org/news/ghc-9-2 Title: GHC 9.2 Authors: Guillaum palm123, Ysabeau đŸ§¶, tisaac et theojouedubanjo Date: 2021ćčŽ08月17æ—„T11:03:48+02:00 License: CC By-SA Tags: ghc et haskell Score: 34 GHC 9.2 est sorti le 29 octobre 2021. Cette nouvelle version du principal compilateur pour Haskell apporte son lot de nouveautĂ©s dĂ©taillĂ©es dans la suite de cette dĂ©pĂȘche. Comme Ă  notre habitude, nous terminerons la dĂ©pĂȘche par un exemple de projet en Haskell. Nous rappelons qu’Haskell est un langage de programmation qui se dĂ©marque par son design. En effet, fort d’un typage statique avec infĂ©rence (i.e. il n’est pas nĂ©cessaire d’écrire les types pour que le langage les vĂ©rifie), son Ă©valuation paresseuse (le code n’est exĂ©cutĂ© que quand c’est strictement nĂ©cessaire) et de sa sĂ©paration des effets, Haskell est un langage ovni dans le marchĂ© qu’il influence depuis de nombreuses annĂ©es. ---- [GHC 9.2.1-alpha2 now available](https://www.haskell.org/ghc/blog/20210422-ghc-9.2.1-alpha2-relased.html) [Note de version](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/9.2.1-notes.html) [Annonce de version](https://discourse.haskell.org/t/ghc-9-2-1-released/3527) ---- # Notes de version ## RecordDotSyntax et NoFieldSelectors Les nouvelles extensions `OverloadedRecordDot` `NoFieldSelectors`, `OverloadedRecordUpdate` ainsi que le support de `DuplicateRecordFields` avec l’extension `PatternSynonyms` impactent un point important du langage : la syntaxe des records. Pour comprendre celui-ci, ainsi que son impact potentiel sur le langage, il faut un peu de contexte. ### Records Haskell permet de dĂ©finir des records, c’est-Ă -dire des types ayant plusieurs champs nommĂ©s. Par exemple : ```haskell data Joueur = Joueur { nom :: String, score :: Int } deriving (Show) ``` La crĂ©ation, mise Ă  jour et lecture des champs d’un `Joueur` se font de la façon suivante, ici dans une session interactive : ```haskell>>> unJoueur = Joueur { nom = "Guillaume", score = 9001 }>>> unJoueur Joueur {nom = "Guillaume", score = 9001}>>> -- Update>>> unAutreJoueur = unJoueur { score = 10000 }>>> unAutreJoueur Joueur {nom = "Guillaume", score = 10000}>>> -- Lecture d’un champ>>> score unAutreJoueur 10000 ``` On rappelle que Haskell est un langage qui privilĂ©gie la non mutabilitĂ©, c’est-Ă -dire que l’on ne peut pas modifier `unJoueur`, il faut donc crĂ©er une nouvelle valeur `unAutreJoueur` Ă  chaque mise Ă  jour. Cette syntaxe de record possĂšde de nombreuses limitations. - Les lectures et mises Ă  jour sur des structures profondes sont gĂ©nĂ©ralement complexes. Par exemple, en imaginant que notre joueur est stockĂ© dans le champ `joueur` d’une autre variable `jeu`, si on veut mettre Ă  jour le score de notre joueur, il faudra Ă©crire : ```haskell nouveauJeu = jeu { joueur1 = joueur1 jeu { score = score (joueur1 jeu) + 1 } } ``` C’est extrĂȘmement verbeux. - La crĂ©ation d’un type (ici `Joueur`) va crĂ©er autant de fonctions qu’il y a de champs. Dans notre exemple prĂ©cĂ©dent, les fonctions `nom` et `score` seront créées. Cela gĂ©nĂšre quantitĂ© de fonctions qui peuvent entrer en conflit avec d’autres fonctions (comme la fonction `id`). - Le langage Haskell ne permet pas, par dĂ©faut, que deux types diffĂ©rents aient les mĂȘmes noms de champs. L’extension `DuplicateRecordFields` supprime cette limitation, cependant les mises Ă  jour et accĂšs aux champs peuvent rester ambigus dans certaines situations. - Il n’est pas possible de rĂ©aliser des fonctions polymorphiques sur les noms de champs. Ainsi, imaginons la fonction suivante: ```haskell afficherNom obj = nom obj ``` On pourrait imaginer que, de façon similaire Ă  Python, ou C++, cette fonction puisse accepter n’importe quel objet Ă  condition qu’il ait un attribut `nom`. HĂ© bien non, cette fonction est ambiguĂ« et le dĂ©veloppeur devra choisir et implĂ©menter autant de fonctions qu’il veut gĂ©rer de types diffĂ©rents, mĂȘme si ces fonctions sont toutes les mĂȘmes. ### Un dĂ©but de solution, `lens` Les « lens » sont un ensemble de fonctionnalitĂ©s qui permettent la manipulation de « chemins » dans des structures de donnĂ©es, puis l’utilisation de ces chemins pour lire (`view`), modifier (`over`) ou Ă©craser (`set`) une donnĂ©e. Le problĂšme est qu’il faut manuellement dĂ©finir des « lens » pour chacun des champs auxquels on souhaite accĂ©der. La librairie `lens` propose de rĂ©aliser cela par le biais de `TemplateHaskell`. ```haskell data Joueur = Joueur { _score :: Int, _nom :: String } deriving (Show) makeLenses ''Joueur data Jeu = Jeu { _joueur1 :: Joueur, _joueur2 :: Joueur } deriving (Show) makeLenses ''Jeu ``` `makeLenses` va gĂ©nĂ©rer les lens `score`, `nom`, `joueur1` et `joueur2`. Maintenant on peut faire des choses : ```haskell>>> unJeu = Jeu { _joueur1 = Joueur { _score = 0, _nom = "Guillaume"}, _joueur2 = Joueur { _score = 0, _nom = "ValĂ©rian" }}>>> unJeu Jeu {_joueur1 = Joueur {_score = 0, _nom = "Guillaume"}, _joueur2 = Joueur {_score = 0, _nom = "Val33rian"}}>>> view (joueur1 . score) unJeu 0>>> unJeu' = set (joueur1 . score) 100 unJeu>>> view (joueur1 . score) unJeu' 100>>> unJeu'' = over (joueur1 . score) (*2) unJeu'>>> unJeu' Jeu {_joueur1 = Joueur {_score = 100, _nom = "Guillaume"}, _joueur2 = Joueur {_score = 0, _nom = "Val33rian"}} ``` Les « lens » rĂšglent le problĂšme de la modification en profondeur d’une structure de donnĂ©e, cependant les problĂšmes de conflit de nom et de mises Ă  jour polymorphiques restent. De plus s’ajoute un nouveau problĂšme. Il existe de nombreuses librairies de lens, avec des approches diffĂ©rentes. On peut citer `lens` et `optics`. Ces librairies sont impressionnantes de fonctionnalitĂ©s (bien que l’on puisse se limiter au sous-ensemble que je viens de prĂ©senter), les erreurs du compilateur peuvent ĂȘtre dures Ă  lire. ### « Generic-lens » Le paquet [generic-lens](https://hackage.haskell.org/package/generic-lens) (et ses variantes pour d’autres type de lens, comme [generic-optics](https://hackage.haskell.org/package/generic-optics)) permettent de gĂ©nĂ©rer des lens avec une syntaxe diffĂ©rente. LĂ  oĂč le package prĂ©cĂ©dent gĂ©nĂ©rait une lens dans l’espace de nom des fonctions (e.g. `score` dans l’exemple d’avant), ces nouveaux packages permettent de crĂ©er des lens en utilisant des chaĂźnes de caractĂšre au niveau du type `field @"joueur1` ou des « labels », `#joueur1`. La lens `joueur1 . score` devient alors `field @"joueur1" . field @"score"` ou `#joueur1 . #score`. L’avantage de cette approche est qu’il n’y a plus de conflit d’espace de nom et que les lens peuvent ĂȘtre polymorphiques, c’est-Ă -dire s’appliquer sur le mĂȘme champ de type diffĂ©rent. Les inconvĂ©nients de ces approches sont les suivants : - la syntaxe est soit « verbeuse » (`field @"joueur1`), ou utilise les labels (e.g. `#joueur1`), nĂ©cessitant `OverloadedLabels`, qui est une syntaxe assez rĂ©cente dans GHC. Celle-ci est peu utilisĂ©e, mal connue, la syntaxe est nouvelle et elle pose son lot de problĂšme. Par exemple, avec la libraire `lens`, les labels gĂ©nĂšrent des instances orphelines. Ce n’est pas le cas avec la bibliothĂšque `optics`. - Cela demande le choix de l’utilisation d’une bibliothĂšque de lens, ce qui limite l’adoption. - GHC va toujours crĂ©er les fonctions pour nos sĂ©lecteurs. Ainsi, deux types ayant les mĂȘmes noms de champs vont gĂ©nĂ©rer les mĂȘmes sĂ©lecteurs et ainsi gĂ©nĂ©rer des conflits, mĂȘme si ceux-ci ne sont pas utilisĂ©s. ### `RecordDotSyntax` et `NoFieldSelectors` L’extension `NoFieldSelectors` permet tout simplement de ne plus exposer les sĂ©lecteurs associĂ©s aux champs d’un type. Cela supprime tout simplement les problĂšmes de conflits discutĂ©s avant. Les extensions `OverloadedRecordDot` et `OverloadedRecordUpdate` permettent tout simplement d’utiliser une syntaxe assez classique dans d’autres langages de programmation, le `.`, pour accĂ©der aux champs. Ainsi, accĂ©der au champ `nom` du `joueur1` du `jeu` se fait grĂące Ă  `jeu.joueur1.nom`. Et la mise Ă  jour en profondeur est aussi possible, par exemple: ```haskell nouveauJeu = jeu { joueur1.score = jeu.joueur1.score + 1 } ``` `OverloadedRecordUpdate` ne permet pas (encore) de mise Ă  jour pouvant changer le type d’un sous champs (ce qui est possible avec les « lens »), et la syntaxe reste plus lourd que les lens dans le cas de mise Ă  jour profonde, comparez l’exemple prĂ©cĂ©dent avec : ```haskell nouveauJeu = over (#joueur1 . #score) (+1) jeu ``` De plus, `OverloadedRecordUpdate` nĂ©cessite que l’utilisateur fournisse une fonction `setField` et `getField`, ainsi cela ne fonctionne pas encore directement. Gageons que de futures versions de GHC fourniront des fonctions adaptĂ©es par dĂ©faut. ### Conclusion Les lens en gĂ©nĂ©ral restent plus puissantes que cette extension, et la librarie `optics`, avec les labels et l’absence de conflit de nom grĂące Ă  `NoFieldSelectors` apportent Ă  mon gout plus de souplesse. Cependant l’arrivĂ©e de ces changements au niveau des records en Haskell apportent une solution « officielle » aux problĂšmes des record et devrait simplifier l’adoption d’Haskell par les dĂ©butants, c’est donc Ă  mon avis une trĂšs bonne nouvelle. ## GHC 2021 Vous le savez sans doute, GHC introduit des nouveautĂ©s vis-Ă -vis du standard Haskell par le biais d’extension. Malheureusement ce mĂ©canisme devient ingĂ©rable tant la [liste d’extensions est longue]( https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/table.html). Chaque fichier Haskell commence gĂ©nĂ©ralement par une liste de multiples extensions, les dĂ©veloppeurs hĂ©sitent Ă  activer certaines d’entre elles. Pour exemple, l’utilisation de syntaxes `0b01` et `0xfe` pour reprĂ©senter des nombres respectivement en notation binaire ou hexadĂ©cimale, nĂ©cessitent l’activation de deux extensions. La nouvelle extension, `GHC2021` regroupe tout un ensemble d’extensions, [46 au total]( https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/control.html#extension-GHC2021) et devrait rĂ©duire le prĂ©ambule des fichiers dans un projet. Le processus qui a permis de sĂ©lectionner [ces extensions](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0380-ghc2021.rst) est particuliĂšrement intĂ©ressant. Chaque extension du langage a Ă©tĂ© notĂ©e en fonction de diffĂ©rents critĂšres tels que son utilisation par la communautĂ©, le risque de « surprise », l’apport au langage... ## En vrac ### Types liftĂ©s - l’extension `UnliftedDataTypes` permet de dĂ©finir des types qui n’acceptent pas d’évaluation paresseuse. Il Ă©tait dĂ©jĂ  possible de forcer l’évaluation par le biais de `BangPatterns` ou de `Strict` et `StrictData`, mais ces extensions n’avaient pas d’impact sur la reprĂ©sentation des donnĂ©es. La nouvelle extension `UnliftedDataTypes` permet ainsi de crĂ©er des types n’acceptant pas d’évaluation paresseuse, et ainsi, dans certains cas, de rĂ©duire leur taille. Cela sera trĂšs utile dans certaines structures de donnĂ©es afin de rĂ©duire les indirections de pointeurs qui coĂ»tent en performance. - liĂ© au point prĂ©cĂ©dent, la reprĂ©sentation des types « lifted » ou « unlifted » (i.e. acceptant ou non une version paresseuse et Ă©tant oui ou non gĂ©rĂ© par le ramasse-miette) Ă©volue et permet de reprĂ©senter des fonctions polymorphiques quelle que soit la reprĂ©sentation des objets utilisĂ©e. Ces deux points vont dans le sens de gĂ©nĂ©rer du code plus efficace avec moins d’indirection (i.e. `UnliftedDataTypes`) sans payer le coĂ»t d’une double implĂ©mentation grĂące aux fonctions polymorphiques sur la reprĂ©sentation. - `ghc-exactprint` est fusionnĂ© dans GHC. La reprĂ©sentation du code aprĂšs parsing conserve les informations de prĂ©sentation tel que les espaces blancs, les retours Ă  la ligne, etc. Ainsi il est possible de parser du code Haskell, faire des modifications, et réécrire ce code sans changer la prĂ©sentation. C’est une grosse avancĂ©e pour l’outillage puisque, par exemple, cela amĂ©liore l’intĂ©gration avec les outils de refactoring d’un IDE qui peuvent maintenant transformer le code (par exemple renommer une variable) sans changer la prĂ©sentation du code. - Il est maintenant possible de gĂ©nĂ©rer de la documentation par le biais de `TemplateHaskell`. En effet, `TemplateHaskell` permet la gĂ©nĂ©ration de code pendant la compilation, mais jusqu’à alors, ce code ne pouvait pas ĂȘtre associĂ© Ă  une documentation, c’est maintenant corrigĂ© grĂące aux fonctions `putDoc` et `getDoc` qui permettent respectivement de gĂ©nĂ©rer une documentation ou de lire une documentation. - l’extension `ImpredicativeTypes` a Ă©tĂ© complĂštement revue et est maintenant considĂ©rĂ©e comme robuste. C’est un dĂ©tail assez complexe du langage invisible pour beaucoup, mais sachez que cela permet l’instanciation de fonctions plus polymorphiques et que cela impacte un opĂ©rateur utilisĂ© tous les jours par les dĂ©veloppeurs Haskell, `$`, qui n’est autre que l’application de fonction (e.g. `f x` et `f $ x` sont identiques). En bref, un cas particulier du langage est maintenant gĂ©rĂ© de maniĂšre robuste et sans cas particulier. Je vous renvoie vers l’article qui traite de cela, https://www.microsoft.com/en-us/research/publication/a-quick-look-at-impredicativity/. - Un gĂ©nĂ©rateur de code natif pour AArch64 est maintenant disponible. Aarch64 Ă©tait dĂ©jĂ  gĂ©rĂ© par GHC par le biais du backend LLVM, mais le gĂ©nĂ©rateur de code natif est plus rapide. - `LinearTypes` peut maintenant infĂ©rer la multiplicitĂ© dans les expressions `case`. Dit autrement, on peut utiliser des `case` avec les types linĂ©aires, ce qui n’était pas possible auparavant, l’algorithme n’arrivant pas Ă  « compter » correctement l’usage des rĂ©fĂ©rences. - Un nouveau warning `-Wredundant-bang-patterns` prĂ©vient lors de l’usage inutile d’un bang (i.e. `!`) sur une donnĂ©e qui est dĂ©jĂ  forcĂ©e. Ce n’est pas forcĂ©ment utile, mais cela peut donner une meilleure comprĂ©hension du code. - Le type `Natural` peut maintenant ĂȘtre promu au niveau du "kind", remplaçant le kind `Nat` qui existait avant. `Natural` reprĂ©sente un entier positif. `Nat` permettait de reprĂ©senter un entier positif paramĂ©trant un type. Par exemple, le kind `Matrix (a :: Nat) (b :: Nat)`, permet de reprĂ©senter par exemple le type `Matrix 4 4`, oĂč `4` est un nombre entier positif, mais connu dans le type et non pas seulement Ă  l’exĂ©cution. La convergence entre `Natural` et `Nat` permet d’écrire des types qui seront utilisĂ©s autant Ă  l’exĂ©cution qu’en tant que kind. - Le type `Char` peut maintenant aussi ĂȘtre promu au niveau du "kind" et de nouvelles "types families" (i.e. fonctions de type) permettent de composer des `Char` ensemble afin de construire des `Symbol` (i.e. des chaines de caractĂšre au niveau du type). Cela ouvre tout un tas de perspectives de programmation au niveau du type. ## Debug Beaucoup de changements de fond qui vont permettre d’amĂ©liorer le processus de debug d’un programme arrivent avec GHC 9.2. ### Origine des allocations La mĂ©thode de `hi-profiling` permet de tagger les objets lors de leur allocation en prĂ©cisant l’origine de l’allocation. Ainsi, lors de l’exĂ©cution, il est possible de savoir d’oĂč viennent les objets encore prĂ©sents en mĂ©moire. Jusqu’à prĂ©sent il Ă©tait possible de connaitre l’usage de la mĂ©moire par type d’objet ou le nombre d’allocation par origine dans le code. Mais une fonction qui alloue beaucoup n’est pas forcĂ©ment une fonction qui utilise beaucoup de mĂ©moire, si les objets allouĂ©s ont une durĂ©e de vie courte. Plus de dĂ©tails dans l’article https://well-typed.com/blog/2021/01/first-look-at-hi-profiling-mode/ ### ghc-debug http://ghc.gitlab.haskell.org/ghc-debug/ permet de se connecter Ă  un programme Haskell en cours d’exĂ©cution et d’interroger l’état de la mĂ©moire. Jusqu’à prĂ©sent, les analyses de mĂ©moire ne pouvaient se faire que statiquement, Ă  la fin de l’exĂ©cution du programme. ## Performances ### GC Parallel Le GC (Garbage Collector) parallĂšle a subi de nombreux changements. Sur les programmes parallĂšles tournant sur plus de 4 "capabilities" (e.g. threads), les temps de pause et le temps CPU utilisĂ© par le GC sont rĂ©duits. C’est une avancĂ©e importante pour le GC parallĂšle qui demandait avant beaucoup de rĂ©glages manuels pour trouver les paramĂštres optimaux. Les dĂ©veloppeurs de GHC vont jusqu’à annoncer que la plupart des rĂ©glages manuels utilisĂ©s avant sont inutiles et que les valeurs par dĂ©faut seront satisfaisantes dans la plupart des cas. Personnellement, j’attends de tester cela en production puisque jusqu’à prĂ©sent, j’avais bien trop souvent tendance Ă  dĂ©sactiver totalement le GC parallĂšle du fait de ses mauvaises performances. ### Autres - Un programme Haskell aura tendance Ă  rendre plus vite la RAM inutilisĂ©e au systĂšme, plutĂŽt que de la conserver. L’impact est faible (puisque la RAM inutilisĂ©e pouvait ĂȘtre mise dans le SWAP), mais cela peut amĂ©liorer la « confiance » en un processus Haskell qui, une fois un pic de consommation passĂ©, affichera une consommation rĂ©duite. - La taille de la nurserie par dĂ©faut passe de 1 MB Ă  4 MB. Cette valeur faisait du sens plusieurs annĂ©es en arriĂšre lorsque la taille des caches des CPUs Ă©tait plus petite. On rappelle que la nurserie est l’endroit ou les objets sont allouĂ©s (avant d’ĂȘtre potentiellement dĂ©placĂ©s), c’est donc un endroit sous haute pression qui vit dans le cache du processeur, l’augmenter Ă  4MB permet d’allouer plus d’objets avant de devoir faire tourner le GC, laissant une plus grande chance aux objets temporaires d’ĂȘtre dĂ©truits et ainsi amĂ©liorant les performances. # Autour de GHC Haskell-language-server, https://hackage.haskell.org/package/haskell-language-server, le LSP pour Haskell est sorti en version 1.4. # Exemple Dans cette section, je voulais parler un peu de formatage. # Haskell et le formatage En Haskell, le formatage est une histoire complexe. Au dĂ©part, on fait tout Ă  la main: ```haskell>>> prenom = "Guillaume">>> age = 35 ->>>> putStrLn ("Bonjour " prenom ". Tu as " show age " ans.") Bonjour Guillaume. Tu as 35 ans. ``` On admettra que cela est peu pratique. C’est difficilement lisible. On se trompe facilement en oubliant un espace. Et on ne peut pas faire de conversion facilement, comme prĂ©ciser le nombre de chiffres significatifs. La libraire `base`, qui vient de base avec GHC, propose `Text.Printf`: ```>>>>>> printf "Bonjour %s. Tu as %d ans.\n" prenom age Bonjour Guillaume. Tu as 35 ans. ``` C’est pratique, cela rend quelques services et cela permet de formater: ```haskell>>> printf "%.3f\n" pi 3.142 ``` Mais cette bibliothĂšque souffre de nombreux dĂ©fauts, et tout particuliĂšrement: - Pas de support des chaines de plusieurs lignes. - Par dĂ©faut, cela gĂ©nĂšre des `String`, dans un monde ou on aimerait plutĂŽt utiliser `Text` - `printf` n’est pas sĂ»r et ainsi peut planter lors de l’exĂ©cution : ```haskell>>> printf "%s" pi *** Exception: printf: bad formatting char 's' ``` Il existe de nombreuses librairies qui proposent des « mini langages » sous forme de fonctions pour faire du formatage. J’apprĂ©cie [fmt](https://hackage.haskell.org/package/fmt-0.6.3.0/docs/Fmt.html), mais cela reste trĂšs verbeux: ```haskell>>> let (a, b, n) = ("foo", "bar", 25)>>> ("Here are some words: "+|a|+", "+|b|+"\nAlso a number: "+|n|+"") :: String "Here are some words: foo, bar\nAlso a number: 25" ``` Et cela ne corrige pas le problĂšme des lignes multiples. ## Comment fait Python ? Avec Python, c’est simple, il existe les `f` string: ```python>>> f"Bonjour {prenom}. Tu as {age} ans. Et pi = {pi:.3f}." 'Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141.' ``` C’est simple, c’est lisible, cela permet le formatage avancĂ©, cela permet les lignes multiples. Seul dĂ©faut, c’est du Python et ce n’est pas sĂ»r. ## PyF [PyF](https://hackage.haskell.org/package/PyF) c’est ma libraire de formatage pour Haskell. J’ai pris les `f` string de Python, j’y ai ajoutĂ© le cĂŽtĂ© vĂ©rifiĂ© Ă  la compilation, et on obtient PyF : ```haskell>>> [fmt|Bonjour {prenom}. Tu as {age} ans. Et pi = {pi:.3f}.|] "Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141." ``` PyF supporte la quasi-totalitĂ© du mini langage de formatage des f string de Python. La derniĂšre version, qui sort en mĂȘme temps que GHC 9.2, a considĂ©rablement rĂ©duit ses dĂ©pendances et ne dĂ©pendant maintenant plus que de `GHC`. De plus, de nouveaux formateurs sont apparus : - `str`: une chaĂźne multi lignes sans formatage - `raw`: une chaĂźne multi lignes sans Ă©chappement - `strTrim` et `fmtTrim`, respectivement une chaĂźne multi lignes sans et avec formatage, mais avec suppression des espaces blancs dans les deux cas. Les versions `trim` sont tout particuliĂšrement utiles pour respecter l’indentation dans un code : ```haskell main = do putStrLn [fmtTrim| Bonjour {nom}, Voici ma liste de course : - Poivrons {nombreDePoivrons} - Lait {volumeDeLait:.1f} |] ``` Ici, les espaces blancs surnumĂ©raires seront supprimĂ©s. # Conclusion GHC 9.2 est lĂ , happy Haskelling. Vous pouvez utiliser `nix`, ou `ghcup`, ou prochainement votre distribution. Ou peut-ĂȘtre un container docker, ou `stack`, bref, essayez GHC 9.2. J'ai tout particuliĂšrement envie de tester : - les meilleures performances du ramasse-miette parallĂšle ; - `NoFieldSelector` pour ne plus avoir de conflit de nom ; - `GHC2021` pour ne plus commencer tous mes programmes Haskell par 40 lignes d’extensions ; - `ghc-debug` pour debuger. Mais je dirais que je suis un utilisateur avancĂ©. J’ai vraiment hĂąte de voir comment les changements sur les records vont aider les dĂ©butants Ă  s’approprier Haskell.

AltStyle ă«ă‚ˆăŁăŠć€‰æ›ă•ă‚ŒăŸăƒšăƒŒă‚ž (->ă‚ȘăƒȘă‚žăƒŠăƒ«) /