GHC 9.2

Posté par  (site web personnel) . Édité par palm123, Ysabeau 🧶 🧦, tisaac et theojouedubanjo. Modéré par patrick_g. Licence CC By‑SA.
Étiquettes :
34
4
nov.
2021
Programmation fonctionnelle

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.

Sommaire

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 :

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 :

>>> 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 :

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:

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.

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 :

>>> 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 (et ses variantes pour d’autres type de lens, comme 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:

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 :

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. 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 et devrait réduire le préambule des fichiers dans un projet.

Le processus qui a permis de sélectionner ces extensions 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, `{mathjax}, 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:

>>> 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:

>>> 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 :
>>> 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, mais cela reste très verbeux:

>>> 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:

>>> 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 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 :

>>> [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 :

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.

Aller plus loin

  • # Coquilles

    Posté par  (site web personnel) . Évalué à 6.

    Est-ce que quelqu'une de la fine équipe de modératisation pourrait corriger les points suivants :

    • Section "Types Liftés":

    utilisé tous les jours par les développeurs Haskell, {mathjax}, qui n’est autre

    Remplacer le {mathjax} par $ et l'exemple qui suit par :

    f x et f $ x sont identiques

    • Dans la section "Haskell et le formatage", de nombreux guillemet anglophone (") ont été remplacés sauvagements par des symboles cabalistiques («) ce qui casse la coloration syntaxique.

    >>> putStrLn (« Bonjour
    -> >>> putStrLn ("Bonjour

    « Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141. »
    -> "Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141."

    Merci beaucoup.

    • [^] # Re: Coquilles

      Posté par  (site web personnel, Mastodon) . Évalué à 4.

      Corrigé pour la deuxième série pour laquelle je plaide coupable. En effet j’étais en train de relire et corriger avec Grammalecte quand la dépêche a été publiée. Du coup, cette bourde que je devais corriger a été oubliée dans la série de petites corrections qui restaient à faire (espaces avant les ponctuations doubles par exemple).

      C’est quelque chose qui ne devrait pas arriver, qu’une dépêche en cours de correction soit publiée. Il a été suggéré dans la tribune de modération que «  le coupable doive faire le tour du quartier tout nu et passer une semaine sous Windows 11 ? ».

      Une semaine sous Windows 11 me paraît un châtiment bien trop fort pour ça (et je ne tiens pas personnellement à assister à l’autre partie de la sentence).

      Sinon, à titre indicatif, ceci : « », ce sont des guillemets typographiques (unicode ab et bb), et ça ‟ ” (unicode 201f et 201d) ce sont des guillemets anglais (il y en a plus). Ça : " (unicode 22) c’est juste une cochonnerie, bonne pour le code mais pour rien d’autre et qu’on appelle une chiure de mouche en typographie (pareil pour ça : ' qui n’a rien à voir avec l’élégante apostrophe typographique ’, code unicode 2019).

      Je n’ai pas trouvé ça : {mathjax}.

      « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

      • [^] # Re: Coquilles

        Posté par  (site web personnel) . Évalué à 2.

        Merci pour les corrections.

        Il reste toujours les problèmes avec f x et f $ x et mathjax, mais cela n’apparaît surement que dans la version finale et pas dans le code markdown d'origine.

        Et pour les guillemets, merci pour les précisions.

        • [^] # Re: Coquilles

          Posté par  (site web personnel, Mastodon) . Évalué à 2.

          Oui c'est tout à fait ça.

          Quoi que j'essaie comme graphie, le symbole dollar $ sème la zone dans cette façon d’écrire :-( Apparemment si ce symbole est encadré par des virgules, des apostrophes ou des parenthèses il est transformé en autre chose.

          Exemple : Haskell, le symbole du dollar , qui n’est autre que l’application de fonction (e.g.f xetf x sont identiques).

          On pourrait rédiger ainsi : Haskell, le symbole du dollar qui n’est autre que l’application de fonction, ainsi, on peut écrire fx avec sans le $ entre f et x (fx = f$x).

          « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

          • [^] # Re: Coquilles

            Posté par  (site web personnel, Mastodon) . Évalué à 2.

            Je me souviens avoir discuté de ça à un autre propos : c'est le parseur Markdown qui détecte une expression mathématique malgré tout ce qu'on fait. C'est inhérent au format et aux extensions ajoutées. Le seul contournement serait alors d'utiliser les blocs de code (ce qui au passage offrira la coloration en bonus.)
            À voir s'il faut faire une entrée de suivi pour cela ; je n'avais pas jugé la chose nécessaire.

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

            • [^] # Re: Coquilles

              Posté par  (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 06 novembre 2021 à 17:29.

              Pour l’entrée de suivi, si ce n'’est pas du ressort de LinuxFr, ça ne servira pa à grand je pense. J’ai hésité aussi d'ailleurs. Je pense que c’est plus une info à ajouter dans l'aide sur la syntaxe Markdown de LinuxFr.

              On pourrait aussi rédiger le truc problématique ainsi :

              (…) Haskell, le symbole du dollar qui n’est autre que l’application de fonction, ainsi, on peut écrire fx avec ou sans le $ entre f et x. En d’autres termes :

                fx est identique à f$x
              

              Je ne vois, en effet, pas d'autre solution.

              La coloration syntaxique n’a aucune importance dans ce cas précis.

              « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

              • [^] # Re: Coquilles

                Posté par  (site web personnel, Mastodon) . Évalué à 3. Dernière modification le 06 novembre 2021 à 17:45.

                En fait il y a un peu des deux… Markdown est lui-même limité (d'où divers ajouts incompatibles entre les uns et les autres) et assez fragile (encore plus avec les ajouts). Linuxfr a ses ajouts/extensions qui interfèrent visiblement ici.

                L'aide à la rédaction, en bas de la fenêtre de commentaire, indique bien la possibilité de rajouter des formules/expressions mathématiques et des/la symboles/notation LaTeX entre dollar et dollar (avec l'exemple \oplus) Bien. Ou entre doubles dollars (avec l'exemple $\frac{1}{x}$) Moins bien visiblement.
                Le souci, est que si je recopie un texte avec deux mentions de prix, par exemple un truc qui coûterait entre sept et neuf dollars (en écrivant « in range $7—$9 » par exemple) ça ne marche heureusement pas (le côté fragile et imprévisible que j'évoquais) Par contre, ça part visiblement en vrille quand on passe en code en ligne (comme dans le cas de « f$x et f$x » alors que le mode code devrait être protégé d'un côté et qu'il n'y a pas les deux dollars de l'autre)

                Sinon j'ai retrouvé le cas auquel j'ai été confronté : https://linuxfr.org/forums/programmation-shell/posts/sollicitation#comment-1866687
                Je n'ai finalement pas fait de suivi car c'est difficile à décrire et bien circoncire (comment résoudre un problème qu'on n'arrive pas à bien poser ?) En tout cas pour moi. Je remarque juste de temps en temps des anomalies que j'arrive à contourner ou pas.

                “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: Coquilles

      Posté par  . Évalué à 1.

      Pour moi il y a aussi une coquille ici :

      (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 étaient plus petites.

      Je pense que l'auteur parle de la taille des caches, donc il faut un singulier et non un pluriel : «lorsque la taille des caches des CPUs était plus petite».

      • [^] # Re: Coquilles

        Posté par  (site web personnel, Mastodon) . Évalué à 3.

        Corrigé, merci (et « il y a quelques années » serait préférable à « plusieurs années en arrière » qui est un pur anglicisme).

        « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

        • [^] # Re: Coquilles

          Posté par  . Évalué à 3.

          Si on veut se lancer dans le nettoyage des anglicismes, toujours dans la même phrase, il y a aussi «Cette valeur faisait du sens» («faire (du) sens» étant le calque de l'anglais ‟make sense”, alors qu'en français on parle d'«avoir du sens»).

    • [^] # Re: Coquilles

      Posté par  . Évalué à 4.

      Hello Guillaum,

      Très belle dépêche, comme d’habitude.

      Serait-il possible d’ajouter ce qu’est le Haskell-language-server et à quoi ça sert ? En suivant les liens, j’ai l’impression que ça permet de mettre des tests dans les commentaires, mais sinon, je n’ai pas tout compris.

      • [^] # Re: Coquilles

        Posté par  . Évalué à 4.

        C'est ce LSP là dont il est question : https://linuxfr.org/news/lsp-le-cadeau-de-microsoft

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

        • [^] # Re: Coquilles

          Posté par  . Évalué à 2.

          Merci pour l’explication.
          J’avoue qu’une extension de l’accronyme avec un lien Wiki aurait pu être sympas.

      • [^] # Re: Coquilles

        Posté par  (site web personnel) . Évalué à 2.

        Merci !;)

        Et désolé, j'ai un peu torché cette partie de la dépêche sur haskell-language-server. Mais en gros cela apporte tous les trucs considerés comme "de base" dans des éditeurs avancés pour des langages classiques.

        Bref, des année que je jalousais les devs C++ dans visual studio, j'ai maintenant la même chose dans vim avec Haskell:

        • Type sous le curseur
        • Documentation
        • Erreur en live
        • Suggestion de correction
        • Import automatiques
        • Refactoring
        • Évaluation de bloc

        Et le plus intéressant c'est que tu peux faire des plugins maison, et donc ajouter n'importe quoi qui soit intéressant seulement pour ton business.

        Je m'en sers sur tous mes projets persos depuis plus d'un an et j'ai mis en place le support (avec bazel ;() dans mes deux derniers boulots et cela marche très bien, sauf qu'il faut une machine avec beaucoup de RAM sur des projets conséquents.

  • # Histoire

    Posté par  . Évalué à 4.

    En Haskell, le formatage est une histoire complexe.

    Quelle partie d'haskell a une histoire simple ? 🙂

    https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Histoire

      Posté par  (site web personnel) . Évalué à 2.

      ;)

      L'histoire de tout est complexe, mais en effet, certains points en Haskell ont encore une histoire complexe (par exemple, le formatting ou les records). Mais j'espere que leur histoire complexe deviennent une histoire simple dans le futur (grace à PyY et GHC 9.2)

  • # Sûreté

    Posté par  (site web personnel) . Évalué à 4.

    Seul défaut, c’est du Python et ce n’est pas sûr.

    Ça semble un peu gratuit, lâché comme ça. Tu veux expliquer en quoi ton format et plus sûr que le format de python ?

    Adhérer à l'April, ça vous tente ?

    • [^] # Re: Sûreté

      Posté par  . Évalué à 4.

      Pas besoin d'aller très loin ;) Chaînes de formatage et sécurité en python (solution au "Petit Défi Python")

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: Sûreté

        Posté par  (site web personnel) . Évalué à 4.

        Merci, je redécouvre ce comportement.

        Même si PyF ne peut pas "injecter" du comportement ou lire une variable non prévue, PyF n'est pas protégé d'un formatage dangereux. Par exemple, en spécifiant une "précision" très grande pour un nombre, on va se retrouver avec une grosse occupation mémoire (e.g. la chaîne générée sera longue), ce qui peut sans doute être exploité pour tuer un service.

        Bref, même en Haskell, il ne faut pas faire confiance aux valeurs qui viennent de l'exterieur.

    • [^] # Re: Sûreté

      Posté par  (site web personnel) . Évalué à 10.

      Merci pour cette question, en effet cela demande des précisions.

      En premier lieu, "mon" format n'est pas plus sûr que le format de python, c'est le même format. Plus exactement, un sous ensemble.

      La réponse de barmic est un cas extrême d'exploitation du coté dynamique de python. Mais j'avoue que je ne pensais même pas à cela en rédigeant ce paragraphe.

      Je parlais de "sûreté" au sens "typage".

      En pratique, le formatage de chaîne python est réalisé dynamiquement : lors de l’exécution, l’interpréteur python va construire la chaîne de formatage, interpréter celle-ci et y insérer les différents expressions / valeurs nécessaire. À ce moment, il peut échouer (et lever une exception) si le formatage demandé n'a pas de sens.

      Par exemple, si le type à formater n'est pas compatible avec le formater, ici une chaîne à formater en octal :

      >>> name = "Guillaume"
      >>> f"Bonjour {name:o}"
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: Unknown format code 'o' for object of type 'str'

      Ou si la syntaxe du formater est incorrecte ou incohérente :

      >>> f"Bonjour {name:l}"
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: Unknown format code 'l' for object of type 'str'
      
      >>> f"Bonjour {3:0.3d}"
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: Precision not allowed in integer format specifier

      Cette erreur arrive lors de l’exécution.

      De son coté, PyF fait ses vérifications lors de la phase de vérification des types, avant l’exécution.

      Prelude PyF> name = "Guillaume"
      Prelude PyF> [fmt|Bonjour {name:o}|]
      
      <interactive>:5:6: error:
           No instance for (Integral [Char])
              arising from a use of PyF.Internal.QQ.formatAnyIntegral
           In the second argument of (<>), namely
              ((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal)
                    PyF.Formatters.Minus)
                   Nothing)
                  Nothing)
                 name
            In the expression:
              (("Bonjour "
                  <>
                    ((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal)
                         PyF.Formatters.Minus)
                        Nothing)
                       Nothing)
                      name))
            In an equation for it:
                it
                  = (("Bonjour "
                        <>
                          ((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal)
                               PyF.Formatters.Minus)
                              Nothing)
                             Nothing)
                            name))
      Prelude PyF> [fmt|Bonjour {name:l}|]
      
      <interactive>:7:6: error:
           <interactive>:1:15:
        |
      1 | Bonjour {name:l}
        |               ^
      unexpected 'l'
      expecting '#', '%', '+', '-', '.', '0', 'E', 'F', 'G', 'X', 'b', 'c', 'd', 'e', 'f', 'g', 'n', 'o', 's', 'x', '}', integer, or space
      
           In the quasi-quotation: [fmt|Bonjour {name:l}|]
      Prelude PyF> [fmt|Bonjour {3:0.3d}|]
      
      <interactive>:6:6: error:
           <interactive>:1:15:
        |
      1 | Bonjour {3:0.3d}
        |               ^
      Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.
      
           In the quasi-quotation: [fmt|Bonjour {3:0.3d}|]

      Ici j'ai exécuté les exemples dans l’interpréteur Haskell, mais je vous demande de me croire que l'erreur est bien une erreur "statique" détectée pendant la compilation et non lors de l’exécution.

      De plus, les erreurs de PyF apportent plus de contexte (notez le petit marqueur ^ qui montre précisément la localisation du problème.) Ce ne fut pas une tache aisée, et c'est loin d'être parfait, mais cela permet tout de même de voir l'erreur pendant la phase de développement et non lors de l’exécution.

      D'ailleurs, pour rentrer dans les détails, PyF peut échouer dans 2 contextes différents.

      • Lors du parsing du la chaîne de formatage ou l'analyse de celle-ci. A ce moment ne sont testé que la cohérence de la chaîne de formatage, et une erreur très précise peut être générée par la libraire.
      • Passé cette étape, la libraire PyF génère du code Haskell et le compilateur va tester la cohérence des expressions. Ici PyF n'a plus aucun contrôle et les erreurs remontées par le compilateur peuvent être un peu complexe à lire, comme ici le No instance for (Integral [Char]) qui veut simplement dire qu'une chaîne de caractère n'est pas convertible en nombre entier. Il y aurait moyen d'améliorer cela un petit peu, il faut que j'y passe un peu de temps, mais il est possible en Haskell de contrôler les message d'erreur des erreurs de type.

      Bref, au final, PyF apporte, comparé à Python, une partie de vérification "statique" lors de la compilation. C'est à mon avis appréciable, après tout, c'est ce que j’apprécie en Haskell, mais ce n'est pas l'apport le plus intéressant qui reste la partie formatage, et là, PyF n'apporte rien de mieux que ce que python propose en standard.

  • # Trop c'est trop !

    Posté par  . Évalué à 4.

    Merci pour le temps passé qu'il a dû falloir pour rédiger cette dépêche.

    Pour le reste, ce que je regrette avec Haskell c'est qu'il est plus long et compliqué d'assimiler les multiples extensions de GHC plutôt que le langage en lui-même qui est plutôt agréable.

    Ce que je veux dire c'est que c'est pas très rassurant pour industrialiser le dev.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.