tag:linuxfr.org,2005:/tags/haskell/publicLinuxFr.org : les contenus étiquetés avec « haskell »2022-05-16T13:36:44+02:00/favicon.pngtag:linuxfr.org,2005:Bookmark/46852022-05-16T13:36:44+02:002022-05-16T13:36:44+02:00Gifcurry: un éditeur de jif animé en Haskell<a href="https://lettier.github.io/gifcurry/">https://lettier.github.io/gifcurry/</a> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/127754/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/devnewton/liens/gifcurry-un-editeur-de-jif-anime-en-haskell#comments">ouvrir dans le navigateur</a>
</p>
devnewton 🍺https://linuxfr.org/nodes/127754/comments.atomtag:linuxfr.org,2005:Post/427382022-03-08T11:25:09+01:002022-03-08T11:59:34+01:00Haskell et elm<p>Bonjour,<br>
Je voudrais faire un petit projet de site web de vote. Pour ce faire, je pensais évidemment à <code>elm</code> pour l’interface côté front. Mais, comme il doit y avoir plusieurs utilisateurs votant, il faut que le front de chacun communique pour dire que son vote est prêt, etc. Donc, il faut communiquer avec le côté back.</p>
<h2 id="toc-Étape-0-préliminaire">Étape 0 (préliminaire)</h2>
<p>Dans la mesure du possible, je voudrais rester en programmation fonctionnelle. Du coup, je pensais faire le back en <code>haskell</code>. Mais quelle bibliothèque choisir ? Y-en-a-t-il qui embarque le serveur ?</p>
<p>Et comment, lier la page fournie par le back pour y mettre le elm ?</p>
<p>Ensuite comment partager les infos entre les différents clients ? J’imagine par websocket, mais j’ai du mal à trouver des exemples simples.</p>
<p>En framework, j’ai vu <a href="http://snapframework.com">Snap</a> qui à l’air pas mal.<br>
J’ai également vu <a href="https://hackage.haskell.org/package/scotty">scotty</a> qui devrait faire l’affaire dans ce projet.</p>
<p>Avez-vous des sites, avec de la documentation ?</p>
<h2 id="toc-Étape1">Étape1</h2>
<p>Créer une petite interface : Nom de la personne, et un bouton ratio pour entrée dans une « salle », ou en créer une.<br>
Si on entre dans une salle, ajouter un champ avec le nom de la salle.<br>
Un bouton « Validé »</p>
<p>Une fois validé, on affiche une page avec les personnes présentes. Un bouton pour valider les présents (uniquement celui qui à créé la salle)</p>
<p>Une fois que le propriétaire valide la salle, on se retrouve en salle de vote. Et on affiche juste un message.</p>
<h2 id="toc-besoin">Besoin</h2>
<p>Pour l’instant, j’en suis à l’étape préliminaire du choix du framework pour commencer mes expérimentation. Je vous fournirai le code au fur et à mesure, histoire d’apprendre et d’améliorer mes compétences. </p>
<p>J’éditerai au fur et à mesure des avancées.</p>
<div><a href="https://linuxfr.org/forums/programmationweb/posts/haskell-et-elm.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/127122/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/forums/programmationweb/posts/haskell-et-elm#comments">ouvrir dans le navigateur</a>
</p>
Anthony Jaguenaudhttps://linuxfr.org/nodes/127122/comments.atomtag:linuxfr.org,2005:Bookmark/39402021-12-02T13:50:57+01:002021-12-02T13:50:57+01:00Évaluons Haskell<a href="https://nyeogmi.com/2021/11/30/assessing-haskell/">https://nyeogmi.com/2021/11/30/assessing-haskell/</a> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/126156/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/chat_de_sorciere/liens/evaluons-haskell#comments">ouvrir dans le navigateur</a>
</p>
Michaëlhttps://linuxfr.org/nodes/126156/comments.atomtag:linuxfr.org,2005:News/406172021-11-04T19:25:35+01:002021-11-05T12:07:34+01:00GHC 9.2Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>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.</p>
<p>Comme à notre habitude, nous terminerons la dépêche par un exemple de projet en Haskell.</p>
<p>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.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.haskell.org/ghc/blog/20210422-ghc-9.2.1-alpha2-relased.html" hreflang="en" href="https://linuxfr.org/redirect/108994">GHC 9.2.1-alpha2 now available</a></li><li>lien nᵒ 2 : <a title="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/9.2.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/108995">Note de version</a></li><li>lien nᵒ 3 : <a title="https://discourse.haskell.org/t/ghc-9-2-1-released/3527" hreflang="en" href="https://linuxfr.org/redirect/109328">Annonce de version</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-notes-de-version">Notes de version</a><ul>
<li>
<a href="#toc-recorddotsyntax-et-nofieldselectors">RecordDotSyntax et NoFieldSelectors</a><ul>
<li><a href="#toc-records">Records</a></li>
<li><a href="#toc-un-d%C3%A9but-de-solution-lens">Un début de solution, <code>lens</code></a></li>
<li><a href="#toc-generic-lens">« Generic-lens »</a></li>
<li><a href="#toc-recorddotsyntax-et-nofieldselectors-1"><code>RecordDotSyntax</code> et <code>NoFieldSelectors</code></a></li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
</li>
<li><a href="#toc-ghc-2021">GHC 2021</a></li>
<li>
<a href="#toc-en-vrac">En vrac</a><ul>
<li><a href="#toc-types-lift%C3%A9s">Types liftés</a></li>
</ul>
</li>
<li>
<a href="#toc-debug">Debug</a><ul>
<li><a href="#toc-origine-des-allocations">Origine des allocations</a></li>
<li><a href="#toc-ghc-debug">ghc-debug</a></li>
</ul>
</li>
<li>
<a href="#toc-performances">Performances</a><ul>
<li><a href="#toc-gc-parallel">GC Parallel</a></li>
<li><a href="#toc-autres">Autres</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#toc-autour-de-ghc">Autour de GHC</a></li>
<li><a href="#toc-exemple">Exemple</a></li>
<li>
<a href="#toc-haskell-et-le-formatage">Haskell et le formatage</a><ul>
<li><a href="#toc-comment-fait-python">Comment fait Python ?</a></li>
<li><a href="#toc-pyf">PyF</a></li>
</ul>
</li>
<li><a href="#toc-conclusion-1">Conclusion</a></li>
</ul>
<h2 id="toc-notes-de-version">Notes de version</h2>
<h3 id="toc-recorddotsyntax-et-nofieldselectors">RecordDotSyntax et NoFieldSelectors</h3>
<p>Les nouvelles extensions <code>OverloadedRecordDot</code> <code>NoFieldSelectors</code>, <code>OverloadedRecordUpdate</code> ainsi que le support de <code>DuplicateRecordFields</code> avec l’extension <code>PatternSynonyms</code> impactent un point important du langage : la syntaxe des records.</p>
<p>Pour comprendre celui-ci, ainsi que son impact potentiel sur le langage, il faut un peu de contexte.</p>
<h4 id="toc-records">Records</h4>
<p>Haskell permet de définir des records, c’est-à-dire des types ayant plusieurs champs nommés. Par exemple :</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Joueur</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span>
<span class="n">nom</span> <span class="ow">::</span> <span class="kt">String</span><span class="p">,</span>
<span class="n">score</span> <span class="ow">::</span> <span class="kt">Int</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">)</span></code></pre>
<p>La création, mise à jour et lecture des champs d’un <code>Joueur</code> se font de la façon suivante, ici dans une session interactive :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">unJoueur</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span> <span class="n">nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">,</span> <span class="n">score</span> <span class="ow">=</span> <span class="mi">9001</span> <span class="p">}</span>
<span class="o">>>></span> <span class="n">unJoueur</span>
<span class="kt">Joueur</span> <span class="p">{</span><span class="n">nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">,</span> <span class="n">score</span> <span class="ow">=</span> <span class="mi">9001</span><span class="p">}</span>
<span class="o">>>></span> <span class="c1">-- Update</span>
<span class="o">>>></span> <span class="n">unAutreJoueur</span> <span class="ow">=</span> <span class="n">unJoueur</span> <span class="p">{</span> <span class="n">score</span> <span class="ow">=</span> <span class="mi">10000</span> <span class="p">}</span>
<span class="o">>>></span> <span class="n">unAutreJoueur</span>
<span class="kt">Joueur</span> <span class="p">{</span><span class="n">nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">,</span> <span class="n">score</span> <span class="ow">=</span> <span class="mi">10000</span><span class="p">}</span>
<span class="o">>>></span> <span class="c1">-- Lecture d’un champ</span>
<span class="o">>>></span> <span class="n">score</span> <span class="n">unAutreJoueur</span>
<span class="mi">10000</span></code></pre>
<p>On rappelle que Haskell est un langage qui privilégie la non mutabilité, c’est-à-dire que l’on ne peut pas modifier <code>unJoueur</code>, il faut donc créer une nouvelle valeur <code>unAutreJoueur</code> à chaque mise à jour.</p>
<p>Cette syntaxe de record possède de nombreuses limitations.</p>
<p>- 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 <code>joueur</code> d’une autre variable <code>jeu</code>, si on veut mettre à jour le score de notre joueur, il faudra écrire :</p>
<pre><code class="haskell"><span class="nf">nouveauJeu</span> <span class="ow">=</span> <span class="n">jeu</span> <span class="p">{</span>
<span class="n">joueur1</span> <span class="ow">=</span> <span class="n">joueur1</span> <span class="n">jeu</span> <span class="p">{</span>
<span class="n">score</span> <span class="ow">=</span> <span class="n">score</span> <span class="p">(</span><span class="n">joueur1</span> <span class="n">jeu</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<p>C’est extrêmement verbeux.</p>
<ul>
<li><p>La création d’un type (ici <code>Joueur</code>) va créer autant de fonctions qu’il y a de champs. Dans notre exemple précédent, les fonctions <code>nom</code> et <code>score</code> seront créées. Cela génère quantité de fonctions qui peuvent entrer en conflit avec d’autres fonctions (comme la fonction <code>id</code>).</p></li>
<li><p>Le langage Haskell ne permet pas, par défaut, que deux types différents aient les mêmes noms de champs. L’extension <code>DuplicateRecordFields</code> supprime cette limitation, cependant les mises à jour et accès aux champs peuvent rester ambigus dans certaines situations.</p></li>
<li><p>Il n’est pas possible de réaliser des fonctions polymorphiques sur les noms de champs. Ainsi, imaginons la fonction suivante:</p></li>
</ul>
<pre><code class="haskell"><span class="nf">afficherNom</span> <span class="n">obj</span> <span class="ow">=</span> <span class="n">nom</span> <span class="n">obj</span></code></pre>
<p>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 <code>nom</code>. 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.</p>
<h4 id="toc-un-début-de-solution-lens">Un début de solution, <code>lens</code>
</h4>
<p>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 (<code>view</code>), modifier (<code>over</code>) ou écraser (<code>set</code>) une donnée.</p>
<p>Le problème est qu’il faut manuellement définir des « lens » pour chacun des champs auxquels on souhaite accéder. La librairie <code>lens</code> propose de réaliser cela par le biais de <code>TemplateHaskell</code>.</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Joueur</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span>
<span class="n">_score</span> <span class="ow">::</span> <span class="kt">Int</span><span class="p">,</span>
<span class="n">_nom</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">)</span>
<span class="nf">makeLenses</span> <span class="kt">''Joueur</span>
<span class="kr">data</span> <span class="kt">Jeu</span> <span class="ow">=</span> <span class="kt">Jeu</span> <span class="p">{</span>
<span class="n">_joueur1</span> <span class="ow">::</span> <span class="kt">Joueur</span><span class="p">,</span>
<span class="n">_joueur2</span> <span class="ow">::</span> <span class="kt">Joueur</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">)</span>
<span class="nf">makeLenses</span> <span class="kt">''Jeu</span></code></pre>
<p><code>makeLenses</code> va générer les lens <code>score</code>, <code>nom</code>, <code>joueur1</code> et <code>joueur2</code>.</p>
<p>Maintenant on peut faire des choses :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">unJeu</span> <span class="ow">=</span> <span class="kt">Jeu</span> <span class="p">{</span> <span class="n">_joueur1</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span> <span class="n">_score</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">},</span> <span class="n">_joueur2</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span> <span class="n">_score</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Valérian"</span> <span class="p">}}</span>
<span class="o">>>></span> <span class="n">unJeu</span>
<span class="kt">Jeu</span> <span class="p">{</span><span class="n">_joueur1</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span><span class="n">_score</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">},</span> <span class="n">_joueur2</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span><span class="n">_score</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Val33rian"</span><span class="p">}}</span>
<span class="o">>>></span> <span class="n">view</span> <span class="p">(</span><span class="n">joueur1</span> <span class="o">.</span> <span class="n">score</span><span class="p">)</span> <span class="n">unJeu</span>
<span class="mi">0</span>
<span class="o">>>></span> <span class="n">unJeu'</span> <span class="ow">=</span> <span class="n">set</span> <span class="p">(</span><span class="n">joueur1</span> <span class="o">.</span> <span class="n">score</span><span class="p">)</span> <span class="mi">100</span> <span class="n">unJeu</span>
<span class="o">>>></span> <span class="n">view</span> <span class="p">(</span><span class="n">joueur1</span> <span class="o">.</span> <span class="n">score</span><span class="p">)</span> <span class="n">unJeu'</span>
<span class="mi">100</span>
<span class="o">>>></span> <span class="n">unJeu''</span> <span class="ow">=</span> <span class="n">over</span> <span class="p">(</span><span class="n">joueur1</span> <span class="o">.</span> <span class="n">score</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="mi">2</span><span class="p">)</span> <span class="n">unJeu'</span>
<span class="o">>>></span> <span class="n">unJeu'</span>
<span class="kt">Jeu</span> <span class="p">{</span><span class="n">_joueur1</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span><span class="n">_score</span> <span class="ow">=</span> <span class="mi">100</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span><span class="p">},</span> <span class="n">_joueur2</span> <span class="ow">=</span> <span class="kt">Joueur</span> <span class="p">{</span><span class="n">_score</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">_nom</span> <span class="ow">=</span> <span class="s">"Val33rian"</span><span class="p">}}</span></code></pre>
<p>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.</p>
<p>De plus s’ajoute un nouveau problème. Il existe de nombreuses librairies de lens, avec des approches différentes. On peut citer <code>lens</code> et <code>optics</code>. 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.</p>
<h4 id="toc-generic-lens">« Generic-lens »</h4>
<p>Le paquet <a href="https://hackage.haskell.org/package/generic-lens">generic-lens</a> (et ses variantes pour d’autres type de lens, comme <a href="https://hackage.haskell.org/package/generic-optics">generic-optics</a>) 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. <code>score</code> 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 <code>field @"joueur1</code> ou des « labels », <code>#joueur1</code>.</p>
<p>La lens <code>joueur1 . score</code> devient alors <code>field @"joueur1" . field @"score"</code> ou <code>#joueur1 . #score</code>.</p>
<p>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.</p>
<p>Les inconvénients de ces approches sont les suivants :</p>
<ul>
<li>la syntaxe est soit « verbeuse » (<code>field @"joueur1</code>), ou utilise les labels (e.g. <code>#joueur1</code>), nécessitant <code>OverloadedLabels</code>, 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 <code>lens</code>, les labels génèrent des instances orphelines. Ce n’est pas le cas avec la bibliothèque <code>optics</code>.</li>
<li>Cela demande le choix de l’utilisation d’une bibliothèque de lens, ce qui limite l’adoption.</li>
<li>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.</li>
</ul>
<h4 id="toc-recorddotsyntax-et-nofieldselectors-1">
<code>RecordDotSyntax</code> et <code>NoFieldSelectors</code>
</h4>
<p>L’extension <code>NoFieldSelectors</code> 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.</p>
<p>Les extensions <code>OverloadedRecordDot</code> et <code>OverloadedRecordUpdate</code> permettent tout simplement d’utiliser une syntaxe assez classique dans d’autres langages de programmation, le <code>.</code>, pour accéder aux champs.</p>
<p>Ainsi, accéder au champ <code>nom</code> du <code>joueur1</code> du <code>jeu</code> se fait grâce à <code>jeu.joueur1.nom</code>. Et la mise à jour en profondeur est aussi possible, par exemple:</p>
<pre><code class="haskell"><span class="nf">nouveauJeu</span> <span class="ow">=</span> <span class="n">jeu</span> <span class="p">{</span>
<span class="n">joueur1</span><span class="o">.</span><span class="n">score</span> <span class="ow">=</span> <span class="n">jeu</span><span class="o">.</span><span class="n">joueur1</span><span class="o">.</span><span class="n">score</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">}</span></code></pre>
<p><code>OverloadedRecordUpdate</code> 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 :</p>
<pre><code class="haskell"><span class="nf">nouveauJeu</span> <span class="ow">=</span> <span class="n">over</span> <span class="p">(</span><span class="o">#</span><span class="n">joueur1</span> <span class="o">.</span> <span class="o">#</span><span class="n">score</span><span class="p">)</span> <span class="p">(</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="n">jeu</span></code></pre>
<p>De plus, <code>OverloadedRecordUpdate</code> nécessite que l’utilisateur fournisse une fonction <code>setField</code> et <code>getField</code>, ainsi cela ne fonctionne pas encore directement. Gageons que de futures versions de GHC fourniront des fonctions adaptées par défaut.</p>
<h4 id="toc-conclusion">Conclusion</h4>
<p>Les lens en général restent plus puissantes que cette extension, et la librarie <code>optics</code>, avec les labels et l’absence de conflit de nom grâce à <code>NoFieldSelectors</code> apportent à mon gout plus de souplesse.</p>
<p>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.</p>
<h3 id="toc-ghc-2021">GHC 2021</h3>
<p>Vous le savez sans doute, GHC introduit des nouveautés vis-à-vis du standard Haskell par le biais d’extension.</p>
<p>Malheureusement ce mécanisme devient ingérable tant la <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/table.html">liste d’extensions est longue</a>. 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 <code>0b01</code> et <code>0xfe</code> pour représenter des nombres respectivement en notation binaire ou hexadécimale, nécessitent l’activation de deux extensions.</p>
<p>La nouvelle extension, <code>GHC2021</code> regroupe tout un ensemble d’extensions, <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/control.html#extension-GHC2021">46 au total</a> et devrait réduire le préambule des fichiers dans un projet.</p>
<p>Le processus qui a permis de sélectionner <a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0380-ghc2021.rst">ces extensions</a> 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…</p>
<h3 id="toc-en-vrac">En vrac</h3>
<h4 id="toc-types-liftés">Types liftés</h4>
<ul>
<li>l’extension <code>UnliftedDataTypes</code> 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 <code>BangPatterns</code> ou de <code>Strict</code> et <code>StrictData</code>, mais ces extensions n’avaient pas d’impact sur la représentation des données. La nouvelle extension <code>UnliftedDataTypes</code> 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>
<li>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.</li>
</ul>
<p>Ces deux points vont dans le sens de générer du code plus efficace avec moins d’indirection (i.e. <code>UnliftedDataTypes</code>) sans payer le coût d’une double implémentation grâce aux fonctions polymorphiques sur la représentation.</p>
<ul>
<li>
<code>ghc-exactprint</code> 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.</li>
<li><p>Il est maintenant possible de générer de la documentation par le biais de <code>TemplateHaskell</code>. En effet, <code>TemplateHaskell</code> 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 <code>putDoc</code> et <code>getDoc</code> qui permettent respectivement de générer une documentation ou de lire une documentation.</p></li>
<li><p>l’extension <code>ImpredicativeTypes</code> 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, `<code>{mathjax}</code>, qui n’est autre que l’application de fonction (e.g. <code>f x</code> et <code>f</code> 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, <a href="https://www.microsoft.com/en-us/research/publication/a-quick-look-at-impredicativity/">https://www.microsoft.com/en-us/research/publication/a-quick-look-at-impredicativity/</a>.</p></li>
<li><p>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.</p></li>
<li><p><code>LinearTypes</code> peut maintenant inférer la multiplicité dans les expressions <code>case</code>. Dit autrement, on peut utiliser des <code>case</code> 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.</p></li>
<li><p>Un nouveau warning <code>-Wredundant-bang-patterns</code> prévient lors de l’usage inutile d’un bang (i.e. <code>!</code>) 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.</p></li>
<li><p>Le type <code>Natural</code> peut maintenant être promu au niveau du "kind", remplaçant le kind <code>Nat</code> qui existait avant. <code>Natural</code> représente un entier positif. <code>Nat</code> permettait de représenter un entier positif paramétrant un type. Par exemple, le kind <code>Matrix (a :: Nat) (b :: Nat)</code>, permet de représenter par exemple le type <code>Matrix 4 4</code>, où <code>4</code> est un nombre entier positif, mais connu dans le type et non pas seulement à l’exécution. La convergence entre <code>Natural</code> et <code>Nat</code> permet d’écrire des types qui seront utilisés autant à l’exécution qu’en tant que kind.</p></li>
<li><p>Le type <code>Char</code> peut maintenant aussi être promu au niveau du "kind" et de nouvelles "types families" (i.e. fonctions de type) permettent de composer des <code>Char</code> ensemble afin de construire des <code>Symbol</code> (i.e. des chaines de caractère au niveau du type). Cela ouvre tout un tas de perspectives de programmation au niveau du type.</p></li>
</ul>
<h3 id="toc-debug">Debug</h3>
<p>Beaucoup de changements de fond qui vont permettre d’améliorer le processus de debug d’un programme arrivent avec GHC 9.2.</p>
<h4 id="toc-origine-des-allocations">Origine des allocations</h4>
<p>La méthode de <code>hi-profiling</code> 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.</p>
<p>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.</p>
<p>Plus de détails dans l’article <a href="https://well-typed.com/blog/2021/01/first-look-at-hi-profiling-mode/">https://well-typed.com/blog/2021/01/first-look-at-hi-profiling-mode/</a></p>
<h4 id="toc-ghc-debug">ghc-debug</h4>
<p><a href="http://ghc.gitlab.haskell.org/ghc-debug/">http://ghc.gitlab.haskell.org/ghc-debug/</a> 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.</p>
<h3 id="toc-performances">Performances</h3>
<h4 id="toc-gc-parallel">GC Parallel</h4>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h4 id="toc-autres">Autres</h4>
<ul>
<li>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.</li>
<li>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.</li>
</ul>
<h2 id="toc-autour-de-ghc">Autour de GHC</h2>
<p>Haskell-language-server, <a href="https://hackage.haskell.org/package/haskell-language-server">https://hackage.haskell.org/package/haskell-language-server</a>, le LSP pour Haskell est sorti en version 1.4.</p>
<h2 id="toc-exemple">Exemple</h2>
<p>Dans cette section, je voulais parler un peu de formatage.</p>
<h2 id="toc-haskell-et-le-formatage">Haskell et le formatage</h2>
<p>En Haskell, le formatage est une histoire complexe.</p>
<p>Au départ, on fait tout à la main:</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">prenom</span> <span class="ow">=</span> <span class="s">"Guillaume"</span>
<span class="o">>>></span> <span class="n">age</span> <span class="ow">=</span> <span class="mi">35</span>
<span class="ow">-></span> <span class="o">>>></span> <span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o"><></span> <span class="n">prenom</span> <span class="o"><></span> <span class="s">". Tu as "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">age</span> <span class="o"><></span> <span class="s">" ans."</span><span class="p">)</span>
<span class="kt">Bonjour</span> <span class="kt">Guillaume</span><span class="o">.</span> <span class="kt">Tu</span> <span class="n">as</span> <span class="mi">35</span> <span class="n">ans</span><span class="o">.</span></code></pre>
<p>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.</p>
<p>La libraire <code>base</code>, qui vient de base avec GHC, propose <code>Text.Printf</code>:</p>
<pre><code>>>> >>> printf "Bonjour %s. Tu as %d ans.\n" prenom age
Bonjour Guillaume. Tu as 35 ans.
</code></pre>
<p>C’est pratique, cela rend quelques services et cela permet de formater:</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">printf</span> <span class="s">"%.3f</span><span class="se">\n</span><span class="s">"</span> <span class="n">pi</span>
<span class="mf">3.142</span></code></pre>
<p>Mais cette bibliothèque souffre de nombreux défauts, et tout particulièrement:</p>
<ul>
<li>Pas de support des chaines de plusieurs lignes.</li>
<li>Par défaut, cela génère des <code>String</code>, dans un monde ou on aimerait plutôt utiliser <code>Text</code>
</li>
<li>
<code>printf</code> n’est pas sûr et ainsi peut planter lors de l’exécution :</li>
</ul>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">printf</span> <span class="s">"%s"</span> <span class="n">pi</span>
<span class="o">***</span> <span class="kt">Exception:</span> <span class="n">printf</span><span class="kt">:</span> <span class="n">bad</span> <span class="n">formatting</span> <span class="n">char</span> <span class="sc">'s'</span></code></pre>
<p>Il existe de nombreuses librairies qui proposent des « mini langages » sous forme de fonctions pour faire du formatage. J’apprécie <a href="https://hackage.haskell.org/package/fmt-0.6.3.0/docs/Fmt.html">fmt</a>, mais cela reste très verbeux:</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="kr">let</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="ow">=</span> <span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">,</span> <span class="mi">25</span><span class="p">)</span>
<span class="o">>>></span> <span class="p">(</span><span class="s">"Here are some words: "</span><span class="o">+|</span><span class="n">a</span><span class="o">|+</span><span class="s">", "</span><span class="o">+|</span><span class="n">b</span><span class="o">|+</span><span class="s">"</span><span class="se">\n</span><span class="s">Also a number: "</span><span class="o">+|</span><span class="n">n</span><span class="o">|+</span><span class="s">""</span><span class="p">)</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="s">"Here are some words: foo, bar</span><span class="se">\n</span><span class="s">Also a number: 25"</span></code></pre>
<p>Et cela ne corrige pas le problème des lignes multiples.</p>
<h3 id="toc-comment-fait-python">Comment fait Python ?</h3>
<p>Avec Python, c’est simple, il existe les <code>f</code> string:</p>
<pre><code class="python"><span class="o">>>></span> <span class="n">f</span><span class="s2">"Bonjour {prenom}. Tu as {age} ans. Et pi = {pi:.3f}."</span>
<span class="s1">'Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141.'</span></code></pre>
<p>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.</p>
<h3 id="toc-pyf">PyF</h3>
<p><a href="https://hackage.haskell.org/package/PyF">PyF</a> c’est ma libraire de formatage pour Haskell. J’ai pris les <code>f</code> string de Python, j’y ai ajouté le côté vérifié à la compilation, et on obtient PyF :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="p">[</span><span class="n">fmt</span><span class="o">|</span><span class="kt">Bonjour</span> <span class="p">{</span><span class="n">prenom</span><span class="p">}</span><span class="o">.</span> <span class="kt">Tu</span> <span class="n">as</span> <span class="p">{</span><span class="n">age</span><span class="p">}</span> <span class="n">ans</span><span class="o">.</span> <span class="kt">Et</span> <span class="n">pi</span> <span class="ow">=</span> <span class="p">{</span><span class="n">pi</span><span class="kt">:.</span><span class="mi">3</span><span class="n">f</span><span class="p">}</span><span class="o">.|</span><span class="p">]</span>
<span class="s">"Bonjour Guillaume. Tu as 35 ans. Et pi = 3.141."</span></code></pre>
<p>PyF supporte la quasi-totalité du mini langage de formatage des f string de Python.</p>
<p>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 <code>GHC</code>. De plus, de nouveaux formateurs sont apparus :</p>
<ul>
<li>
<code>str</code>: une chaîne multi lignes sans formatage</li>
<li>
<code>raw</code>: une chaîne multi lignes sans échappement</li>
<li>
<code>strTrim</code> et <code>fmtTrim</code>, respectivement une chaîne multi lignes sans et avec formatage, mais avec suppression des espaces blancs dans les deux cas.</li>
</ul>
<p>Les versions <code>trim</code> sont tout particulièrement utiles pour respecter l’indentation dans un code :</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">putStrLn</span> <span class="p">[</span><span class="n">fmtTrim</span><span class="o">|</span>
<span class="kt">Bonjour</span> <span class="p">{</span><span class="n">nom</span><span class="p">},</span>
<span class="kt">Voici</span> <span class="n">ma</span> <span class="n">liste</span> <span class="n">de</span> <span class="n">course</span> <span class="kt">:</span>
<span class="o">-</span> <span class="kt">Poivrons</span> <span class="p">{</span><span class="n">nombreDePoivrons</span><span class="p">}</span>
<span class="o">-</span> <span class="kt">Lait</span> <span class="p">{</span><span class="n">volumeDeLait</span><span class="kt">:.</span><span class="mi">1</span><span class="n">f</span><span class="p">}</span>
<span class="o">|</span><span class="p">]</span></code></pre>
<p>Ici, les espaces blancs surnuméraires seront supprimés.</p>
<h2 id="toc-conclusion-1">Conclusion</h2>
<p>GHC 9.2 est là, happy Haskelling.</p>
<p>Vous pouvez utiliser <code>nix</code>, ou <code>ghcup</code>, ou prochainement votre distribution. Ou peut-être un container docker, ou <code>stack</code>, bref, essayez GHC 9.2.</p>
<p>J'ai tout particulièrement envie de tester :</p>
<ul>
<li>les meilleures performances du ramasse-miette parallèle ;</li>
<li>
<code>NoFieldSelector</code> pour ne plus avoir de conflit de nom ;</li>
<li>
<code>GHC2021</code> pour ne plus commencer tous mes programmes Haskell par 40 lignes d’extensions ;</li>
<li>
<code>ghc-debug</code> pour debuger.</li>
</ul>
<p>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.</p>
</div><div><a href="https://linuxfr.org/news/ghc-9-2.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/125162/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/ghc-9-2#comments">ouvrir dans le navigateur</a>
</p>
Guillaumpalm123Ysabeau 🧶 🧦tisaactheojouedubanjohttps://linuxfr.org/nodes/125162/comments.atomtag:linuxfr.org,2005:News/392892021-03-10T23:14:11+01:002021-03-11T08:36:08+01:00GHC 8.8, 8.10 et 9.0Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>GHC (<em>Glasgow Haskell Compiler</em>) 8.8 est sorti le 26 août 2019. GHC 8.10 est sorti le 24 mars 2020. GHC 9.0 vient de sortir, le 4 février 2021.</p>
<p>C’est donc l’occasion de revenir sur les changements de ces versions du principal compilateur pour le langage fonctionnel <a href="https://fr.wikipedia.org/wiki/Haskell" title="Définition Wikipédia">Haskell</a>. Avec 3 versions majeures sur plus de 2 ans, tout ne peut pas rentrer dans une dépêche. De plus, beaucoup de détails ne sont pas forcément passionnants ou adaptés à un public non expert en compilation, c’est pourquoi cette dépêche se focalise sur les exemples que nous avons jugés intéressants, autant pour les nouveautés que pour l’opportunité de présenter des points saillants du langage.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.haskell.org/ghc/blog/20190825-ghc-8.8.1-released.html" hreflang="en" href="https://linuxfr.org/redirect/104691">GHC 8.8 - Annonce</a></li><li>lien nᵒ 2 : <a title="https://downloads.haskell.org/ghc/8.8.1/docs/html/users_guide/8.8.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/107474">GHC 8.8 - Notes de version</a></li><li>lien nᵒ 3 : <a title="https://www.haskell.org/ghc/blog/20200324-ghc-8.10.1-released.html" hreflang="en" href="https://linuxfr.org/redirect/107475">GHC 8.10 - Annonce</a></li><li>lien nᵒ 4 : <a title="https://downloads.haskell.org/ghc/8.10.1/docs/html/users_guide/8.10.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/107476">GHC 8.10 - Notes de version</a></li><li>lien nᵒ 5 : <a title="https://discourse.haskell.org/t/ghc-9-0-1-released/1840" hreflang="en" href="https://linuxfr.org/redirect/107477">GHC 9.0 - Annonce</a></li><li>lien nᵒ 6 : <a title="https://downloads.haskell.org/ghc/9.0.1-rc1/docs/html/users_guide/9.0.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/107478">GHC 9.0 - Notes de version</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-application-de-kind">Application de kind</a></li>
<li>
<a href="#toc-am%C3%A9lioration-du-test-dexhaustivit%C3%A9-de-lanalyse-de-cas">Amélioration du test d’exhaustivité de l’analyse de cas</a><ul>
<li><a href="#toc-cas-impossibles">Cas impossibles</a></li>
<li><a href="#toc-exhaustivit%C3%A9-profonde">Exhaustivité profonde</a></li>
</ul>
</li>
<li><a href="#toc-importqualifiedpost">ImportQualifiedPost</a></li>
<li><a href="#toc-instances">:instances</a></li>
<li><a href="#toc-nouveau-gc-%C3%A0-faible-latence">Nouveau GC à faible latence</a></li>
<li><a href="#toc-interface-pour-les-bigs-nums">Interface pour les bigs nums</a></li>
<li><a href="#toc-lexicalnotation">LexicalNotation</a></li>
<li><a href="#toc-lineartypes">LinearTypes</a></li>
<li><a href="#toc-qualifieddo">QualifiedDo</a></li>
<li><a href="#toc-haskell-language-server">Haskell Language Server</a></li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<p><em>Note sur les extensions</em> : Dans cette dépêche nous parlons souvent d’extension de GHC. Celles-ci s’activent en haut de vos fichiers Haskell grâce au <em>pragma</em> <code>LANGUAGE</code>, de la façon suivante :</p>
<pre><code class="haskell"><span class="cm">{-# LANGUAGE TypeApplications #-}</span>
<span class="cm">{-# LANGUAGE Strict #-}</span>
<span class="cm">{-# LANGUAGE DataKinds #-}</span>
<span class="cm">{-# LANGUAGE BinaryLiterals #-}</span></code></pre>
<p>En effet, GHC, par défaut, est compatible avec le <a href="https://www.haskell.org/onlinereport/haskell2010/">Haskell report 2010</a>. Toute modification du langage se fait par le biais d’extensions qu’il est possible d’activer (ou de désactiver).</p>
<h2 id="toc-application-de-kind">Application de kind</h2>
<p>GHC 8.8 étend la syntaxe d’application de type aux <a href="https://wiki.haskell.org/Kind">kinds</a> suite à la proposition <a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0080-type-level-type-applications.rst">GHC Proposal #15</a>. Cette section fait quelques rappels sur l’inférence de type et sur l’application de type, pour ensuite présenter l’application de kind.</p>
<p>Grâce à l’inférence de type, il est rarement nécessaire de préciser les types des expressions que nous manipulons en Haskell. Cependant certains cas peuvent être ambigus :</p>
<pre><code class="haskell"><span class="nf">foo</span> <span class="ow">::</span> <span class="kt">String</span> <span class="ow">-></span> <span class="kt">String</span>
<span class="nf">foo</span> <span class="n">x</span> <span class="ow">=</span> <span class="n">show</span> <span class="p">(</span><span class="n">read</span> <span class="n">x</span><span class="p">)</span></code></pre>
<p>Ici, la fonction <code>foo</code> accepte un argument <code>x</code> qui sera ensuite passé à la fonction <code>read</code> puis à <code>show</code>. Sans rentrer dans les détails, sachez que <code>read</code> peut convertir une chaîne de caractères en n’importe quel type et que <code>show</code> peut convertir n’importe quel type en chaîne de caractères.</p>
<p>Ainsi il est évident que <code>x</code> est une chaîne de caractères et que le résultat de <code>foo</code> est aussi une chaîne de caractères. Mais quel est le type intermédiaire, renvoyé par <code>read</code> et lu par <code>show</code> ?</p>
<p>Dans ces rares cas ambigus, il est possible de préciser le type intermédiaire grâce à une annotation de type ou grâce à la syntaxe de <code>TypeApplication</code> de la manière suivante :</p>
<pre><code class="haskell"><span class="c1">-- Annotation de type</span>
<span class="nf">foo</span> <span class="n">x</span> <span class="ow">=</span> <span class="n">show</span> <span class="p">(</span><span class="n">read</span> <span class="n">x</span> <span class="ow">::</span> <span class="kt">Bool</span><span class="p">)</span>
<span class="c1">-- TypeApplication</span>
<span class="nf">foo</span> <span class="n">x</span> <span class="ow">=</span> <span class="n">show</span> <span class="o">@</span><span class="kt">Bool</span> <span class="p">(</span><span class="n">read</span> <span class="n">x</span><span class="p">)</span></code></pre>
<p>Dans ce contexte, <code>:: Bool</code> ou <code>@Bool</code> précisent que l’argument de <code>show</code> sera un booléen. <code>read</code> va donc devoir produire un <code>Bool</code>.</p>
<p><code>TypeApplication</code> devient plus confortable que l’annotation de type quand la fonction travail sur un type polymorphique plus complexe. Par exemple, la fonction <code>try</code> suivante :</p>
<pre><code class="haskell"><span class="nf">try</span> <span class="ow">::</span> <span class="kt">Exception</span> <span class="n">e</span> <span class="ow">=></span> <span class="kt">IO</span> <span class="n">a</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Either</span> <span class="n">e</span> <span class="n">a</span><span class="p">)</span></code></pre>
<p>Est utilisé pour la gestion d’exception. Le type <code>e</code> est important, car il détermine l’exception qui sera levée. Cependant, cela commence à devenir verbeux d’utiliser une annotation de type, voir les différents exemples :</p>
<pre><code class="haskell"><span class="c1">-- Exemple 1 : annotation sur `try`</span>
<span class="nf">res</span> <span class="ow"><-</span> <span class="p">(</span><span class="n">try</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">String</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Either</span> <span class="kt">SomeException</span> <span class="kt">String</span><span class="p">))</span> <span class="p">(</span><span class="n">readFile</span> <span class="n">fileName</span><span class="p">)</span>
<span class="kr">case</span> <span class="n">res</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="n">e</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Error"</span>
<span class="kt">Right</span> <span class="n">s</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Ok"</span></code></pre>
<pre><code class="haskell"><span class="c1">-- Exemple 2 : annotation sur le retour de `try`</span>
<span class="nf">res</span> <span class="ow"><-</span> <span class="p">(</span><span class="n">try</span> <span class="p">(</span><span class="n">readFile</span> <span class="n">fileName</span><span class="p">))</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Either</span> <span class="kt">SomeException</span> <span class="kt">String</span><span class="p">)</span>
<span class="kr">case</span> <span class="n">res</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="n">e</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Error"</span>
<span class="kt">Right</span> <span class="n">s</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Ok"</span></code></pre>
<pre><code class="haskell"><span class="c1">-- Exemple 3 : annotation sur le binding de res</span>
<span class="nf">res</span> <span class="ow">::</span> <span class="kt">Either</span> <span class="kt">SomeException</span> <span class="kt">String</span> <span class="ow"><-</span> <span class="n">try</span> <span class="p">(</span><span class="n">readFile</span> <span class="n">fileName</span><span class="p">)</span>
<span class="kr">case</span> <span class="n">res</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="n">e</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Error"</span>
<span class="kt">Right</span> <span class="n">s</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Ok"</span></code></pre>
<pre><code class="haskell"><span class="c1">-- Exemple 4 : annotation sur le binding de e</span>
<span class="nf">res</span> <span class="ow"><-</span> <span class="n">try</span> <span class="p">(</span><span class="n">readFile</span> <span class="n">fileName</span><span class="p">)</span>
<span class="kr">case</span> <span class="n">res</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="p">(</span><span class="n">e</span> <span class="ow">::</span> <span class="kt">SomeException</span><span class="p">)</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Error"</span>
<span class="kt">Right</span> <span class="n">s</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Ok"</span></code></pre>
<p>On note que l’annotation de type est nécessaire parce qu’il n’est pas fait d’usage de <code>e</code>, ainsi le compilateur ne peut pas inférer le type.</p>
<p>Toutes ces solutions sont verbeuses ou se produisent loin du site d’appel. Grace à <code>TypeApplication</code>, on obtient une syntaxe plus compacte :</p>
<pre><code class="haskell"><span class="nf">res</span> <span class="ow"><-</span> <span class="n">try</span> <span class="o">@</span><span class="kt">SomeException</span> <span class="p">(</span><span class="n">readFile</span> <span class="n">fileName</span><span class="p">)</span>
<span class="kr">case</span> <span class="n">res</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="n">e</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Error"</span>
<span class="kt">Right</span> <span class="n">s</span> <span class="ow">-></span> <span class="n">print</span> <span class="s">"Ok"</span></code></pre>
<p><em>Note</em> : Dans ce cas, nous sommes un peu chanceux. <code>try</code> est polymorphique sur deux types, <code>e</code> et <code>a</code>, comme montré dans sa signature. La syntaxe <code>try @SomeException</code> permet de préciser le premier type, ici <code>e</code>. Mais si nous avions voulu préciser le second, il aurait fallu utiliser un wildcard, <code>try @_ @String</code> ou être exhaustif, <code>try @SomeException @String</code>. L’ordre de déclaration des types polymorphiques d’une fonction n’est pas arbitraire et a donc un impact sur l’interface publique des fonctions.</p>
<p>C’est assez rare d’avoir besoin de cette syntaxe pour lever des cas ambigus, mais certaines bibliothèques qui exploitent dans ses limites le système de type en ont besoin.</p>
<p>GHC 8.8 permet maintenant de réaliser le même type d’annotations, cependant au niveau du <em>kind</em>, c’est-à-dire le type d’un type.</p>
<p>Par exemple, la fonction <code>Just :: t -> Maybe t</code> peut utiliser <code>TypeApplication</code> tel que <code>Just @Int</code> soit de type <code>Int -> Maybe Int</code>. De manière similaire, la fonction sur les types <code>'Just :: k -> Maybe k</code> peut utiliser <code>TypeApplication</code> tel que <code>'Just @Nat</code> soit de <em>kind</em> <code>Nat -> Maybe Nat</code>.</p>
<h2 id="toc-amélioration-du-test-dexhaustivité-de-lanalyse-de-cas">Amélioration du test d’exhaustivité de l’analyse de cas</h2>
<p>Haskell permet le « pattern matching », ou l’analyse de cas. C’est une version améliorée du <code>switch</code> bien connu en C, car elle permet de déconstruire des types en profondeur.</p>
<p>Prenons un exemple avec le type <code>Couleur</code> :</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Couleur</span> <span class="ow">=</span> <span class="kt">Verte</span> <span class="o">|</span> <span class="kt">Rouge</span> <span class="o">|</span> <span class="kt">Bleue</span> <span class="o">|</span> <span class="kt">Gris</span> <span class="kt">Float</span></code></pre>
<p>Ici, le type couleur peut prendre plusieurs valeurs, simples, comme <code>Verte</code>, <code>Rouge</code> ou <code>Bleue</code>, ou plus complexe comme <code>Gris 0.5</code> qui pourrait representer un gris « moyen ».</p>
<p>Nous pouvons ensuite réaliser une analyse de cas sur cette valeur :</p>
<pre><code class="haskell"><span class="kr">case</span> <span class="n">uneCouleur</span> <span class="kr">of</span>
<span class="kt">Verte</span> <span class="ow">-></span> <span class="s">"Les petits hommes verts"</span>
<span class="kt">Rouge</span> <span class="ow">-></span> <span class="s">"Meurs pourriture communiste"</span>
<span class="kt">Bleue</span> <span class="ow">-></span> <span class="s">"Le bleu du ciel n'est pas le bleu de la mer"</span>
<span class="kt">Gris</span> <span class="mf">0.5</span> <span class="ow">-></span> <span class="s">"J'aime cette nuance de gris"</span></code></pre>
<p>Le compilateur GHC est capable de produire des warnings dans les cas suivants :</p>
<ul>
<li>Si un cas n’est pas traité, ce qui pourrait générer une erreur lors de l’exécution. On dit alors que le code n’est pas « total » ou « exhaustif ».</li>
<li>Si un cas est traité de façon redondante. Dans ce cas, il y a du code « mort ».</li>
</ul>
<p>Cependant, cette analyse n’est pas triviale et dans certains cas GHC peut se tromper et réaliser des faux négatifs / positifs.</p>
<p>GHC 8.10 et 9.0 améliorent la situation dans deux cas :</p>
<ul>
<li>Les cas impossibles</li>
<li>La composition de <code>case</code>.</li>
</ul>
<h3 id="toc-cas-impossibles">Cas impossibles</h3>
<p>Nous allons imaginer un type permettant de représenter la présence ou non d’une annotation :</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Annotation</span> <span class="n">t</span> <span class="ow">=</span> <span class="kt">Vide</span> <span class="o">|</span> <span class="kt">Note</span> <span class="o">!</span><span class="n">t</span></code></pre>
<p>Ici, le type <code>Annotation</code> permet deux cas, <code>Vide</code>, qui représente l’absence d’annotation, et <code>Note x</code>, qui représente une annotation. Le type est polymorphique, ainsi on peut stocker n’importe quoi dedans, comme un <code>Double</code> ou une <code>String</code>, par exemple <code>Note 10.2</code> ou <code>Note "Bonjour"</code>.</p>
<p><em>Digression</em>. Le lecteur attentif aura remarqué la présence d’un <code>!</code> dans la définition du cas <code>Note</code>. Ce symbole, appelé <code>Bang</code>, force l’évaluation de la valeur contenue dans <code>Note</code>. Malheureusement une valeur paresseuse ici rendrait l’exemple faux pour un lecteur très attentif.</p>
<p>Ainsi, on va pouvoir écrire une fonction telle que :</p>
<pre><code class="haskell"><span class="nf">foo</span> <span class="ow">::</span> <span class="kt">Annotation</span> <span class="n">t</span> <span class="ow">-></span> <span class="kt">String</span>
<span class="nf">foo</span> <span class="kt">Vide</span> <span class="ow">=</span> <span class="s">"L'annotation est vide"</span>
<span class="nf">foo</span> <span class="p">(</span><span class="kt">Note</span> <span class="kr">_</span><span class="p">)</span> <span class="ow">=</span> <span class="s">"L'annotation est pleine"</span></code></pre>
<p>On note aussi ici que l’analyse de cas en Haskell se fait autant avec un <code>case</code> que lors de la déclaration d’une fonction.</p>
<p>Ne pas traiter l’un des deux cas revient à écrire une fonction « partielle » et GHC va générer un message.</p>
<p>Cependant, il existe un type en Haskell qui s’appelle <code>Void</code>. Celui-ci est surprenant car bien que le type existe, il n’est pas possible de construire une valeur de ce type. Ainsi, le type <code>Annotation Void</code> n’admet qu’un unique cas, <code>Vide</code>. Ainsi, une fonction traitant une <code>Annotation Void</code> sera complète en ne traitant que le cas <code>Vide</code>, tel que :</p>
<pre><code class="haskell"><span class="nf">bar</span> <span class="ow">::</span> <span class="kt">Annotation</span> <span class="kt">Void</span> <span class="ow">-></span> <span class="kt">String</span>
<span class="nf">bar</span> <span class="kt">Vide</span> <span class="ow">=</span> <span class="s">"C'est vide"</span></code></pre>
<p>Cette fonction n’est pas très intéressante, mais elle est totale. Tous les cas sont traités, car il n’est pas possible de construire un cas <code>Note x</code> car <code>x</code> serait de type <code>Void</code> et il n’est pas possible de construire une valeur de type <code>Void</code> quand il est strict, d’où la présence du symbol <code>!</code> dans la définition de <code>Note</code>.</p>
<p><em>Digression 2</em>. Plus exactement, tout type non strict en Haskell admet une valeur particulière, nommée /bottom/ qui représente un calcul qui ne peut pas se terminer, comme <code>let x = x + 1 in x</code>. Ainsi, il existe une valeur possible pour <code>Void</code>, mais celle-ci représente un calcul qui ne se termine pas. L’annotation <code>!</code> permet de dire au compilateur que la valeur contenue dans <code>Note</code> doit être forcée. Comme il n’est pas possible de forcer /bottom/, alors cela veut dire qu’il n’existe pas de valeur possible pour <code>Void</code>.</p>
<p>Avant GHC 8.10, celui-ci ne réalisait pas que cette fonction était totale et générait un message inadapté. C’est maintenant réglé.</p>
<h3 id="toc-exhaustivité-profonde">Exhaustivité profonde</h3>
<p>Reprenons une analyse de cas sur notre type <code>Couleur</code> :</p>
<pre><code class="haskell"><span class="kr">case</span> <span class="n">c</span> <span class="kr">of</span>
<span class="kt">Rouge</span> <span class="ow">-></span> <span class="s">"Rouge"</span>
<span class="kt">Bleue</span> <span class="ow">-></span> <span class="s">"Bleue"</span>
<span class="kt">Grey</span> <span class="mi">0</span> <span class="ow">-></span> <span class="s">"Noire"</span>
<span class="kt">Grey</span> <span class="mi">1</span> <span class="ow">-></span> <span class="s">"Blanche"</span>
<span class="kr">_</span> <span class="ow">-></span> <span class="kr">case</span> <span class="n">c</span> <span class="kr">of</span>
<span class="kt">Verte</span> <span class="ow">-></span> <span class="s">"Verte"</span>
<span class="kt">Grey</span> <span class="kr">_</span> <span class="ow">-></span> <span class="s">"C'est du gris"</span></code></pre>
<p>Cette fonction est totale, car tous les cas sont bien gérés par la combinaison des deux <code>case</code>. Mais si on regarde localement, on voit que si le premier <code>case</code> gère tous les cas, le second ne traite que <code>Verte</code> et <code>Grey _</code>, sans traiter <code>Rouge</code> ni <code>Bleue</code>. Cela reste cependant correct puisque ces cas sont traités avant.</p>
<p>Avant GHC 9.0, ce cas donnait lieu à un message plutôt agaçant. Le code était correct, mais GHC ne s’en rendait pas compte. GHC 9.0 a revu en profondeur l’algorithme chargé de vérifier l’exhaustivité de l’analyse de cas, rendant celle-ci plus rapide mais surtout, le nouvel algorithme ne se limite plus à une analyse locale et produit donc des messages plus pertinents.</p>
<h2 id="toc-importqualifiedpost">ImportQualifiedPost</h2>
<p>Une nouvelle extension fait son apparition dans GHC 8.10. <code>ImportQualifiedPost</code> permet maintenant de faire des import qualifiés avec une nouvelle syntaxe, comparons :</p>
<pre><code class="haskell"><span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Foo</span></code></pre>
<p>Avec :</p>
<pre><code class="haskell"><span class="cm">{-# LANGUAGE ImportQualifiedPost #-}</span>
<span class="kr">import</span> <span class="nn">Foo</span> <span class="n">qualified</span></code></pre>
<p>J’avais personnellement fait <a href="https://github.com/tweag/ghc-proposals/blob/qualified-import/proposals/0000-default-qualified-import.rst">une proposition</a> que j’estime bien plus ambitieuse sur la syntaxe d’import, mais celle-ci n’aura pas été retenue.</p>
<h2 id="toc-instances">:instances</h2>
<p>Une nouvelle commande pour GHCi 8.10, <code>:instances</code>, qui permet de lister les instances de classe disponibles pour un type. C’est assez pratique pour comprendre ce qui est possible avec un type :</p>
<pre><code class="haskell"><span class="o">></span> <span class="kt">:</span><span class="n">instances</span> <span class="kt">Int</span>
<span class="kr">instance</span> <span class="kt">Eq</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Classes’</span>
<span class="kr">instance</span> <span class="kt">Ord</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Classes’</span>
<span class="kr">instance</span> <span class="kt">Enum</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Enum’</span>
<span class="kr">instance</span> <span class="kt">Num</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Num’</span>
<span class="kr">instance</span> <span class="kt">Real</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Real’</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Show’</span>
<span class="kr">instance</span> <span class="kt">Read</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Read’</span>
<span class="kr">instance</span> <span class="kt">Bounded</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Enum’</span>
<span class="kr">instance</span> <span class="kt">Integral</span> <span class="kt">Int</span> <span class="c1">-- Defined in ‘GHC.Real’</span></code></pre>
<p>Ici nous pouvons voir que le type <code>Int</code> est instance de nombreuses classes (ou « interfaces »), incluant la comparaison (<code>Eq</code>), la relation d’ordre (<code>Ord</code>), l’énumération (<code>Enum</code>)…</p>
<h2 id="toc-nouveau-gc-à-faible-latence">Nouveau GC à faible latence</h2>
<p>Haskell est un langage avec un <a href="https://fr.wikipedia.org/wiki/Ramasse-miettes_(informatique)">ramasse-miettes</a> (GC). Cela signifie que les allocations et la libération de la mémoire se font automatiquement. Dans le cas de GHC, le ramasse-miettes va « stop the world » lors de son exécution. Pour simplifier, disons que le programme Haskell va s’arrêter le temps de laisser au ramasse-miettes le temps de faire son travail.</p>
<p>Le design actuel du GC dans GHC cherchait à minimiser le temps passé dans le GC, ce qui minimise directement le temps d’exécution du programme. C’est le bon compromis quand on s’intéresse uniquement au temps total d’exécution.</p>
<p>Cependant, dans un cas où l’on s’intéresse au temps de réponse de l’application, on pouvait malheureusement se retrouver avec de longues pauses à intervalles non réguliers. C’est fort désagréable dans des applications temps réel. Imaginez un jeu vidéo qui tourne très bien (disons 100 images par seconde) mais qui toutes les minutes, fait une pause d’une seconde.</p>
<p>Une alternative proposée depuis GHC 8.10 est de ne pas toujours arrêter les threads d’exécution (appelés <em>mutators</em>) et dans certains cas d’exécuter le ramasse-miettes en parallèle. Cette solution est moins efficace en termes de performances brutes, car elle se fait au prix de synchronisations coûteuses entre le GC et l’exécution, mais elle a le bénéfice de ne plus provoquer de pause longue et imprédictible lors de l’exécution. Pour reprendre la métaphore du jeu vidéo, celui-ci tourne maintenant à 90 images par secondes, mais ne fait plus de pause.</p>
<p>Je vous invite à lire cet <a href="https://www.well-typed.com/blog/2019/10/nonmoving-gc-merge/">article sur le GC</a> qui détaille le travail réalisé.</p>
<h2 id="toc-interface-pour-les-bigs-nums">Interface pour les bigs nums</h2>
<p>GHC propose des types entiers de taille infinie, les types <code>Integer</code> et <code>Natural</code>, respectivement signé et non signé. Derrière se cache la célèbre bibliothèque <code>gmp</code>. Cela permet par exemple de calculer des factorielles :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">product</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">100</span><span class="p">]</span>
<span class="mi">93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000</span></code></pre>
<p>GHC 9.0 propose maintenant une interface générique au-dessus de <code>gmp</code> et une bibliothèque écrite en Haskell native. Cette dernière ne rivalise pas avec <code>gmp</code> en termes de performance, mais se comporte mieux que l’ancienne implémentation alternative <code>integer-simple</code>.</p>
<p>On peut voir plusieurs raisons à l’usage d’une implémentation alternative, comme la licence, ou tout simplement, l’utilisation d’une bibliothèque écrite en pur Haskell va simplifier la génération de code pour des cibles alternatives comme le javascript.</p>
<h2 id="toc-lexicalnotation">LexicalNotation</h2>
<p>Le moins (i.e. <code>-</code>) est un opérateur qui peut être à la fois binaire, <code>a - b</code>, et unaire, <code>-x</code>. Cela pose de nombreux problèmes en termes de syntaxe, dans les parseurs.</p>
<p>En Haskell, comment se lit <code>f -3</code> ? Est-ce la fonction <code>f</code> appliquée a <code>-3</code>, comme on pourrait écrire <code>f(-3)</code> dans d’autres langages. Ou est-ce la valeur <code>f</code> à qui on soustrait <code>3</code>? Dans ce contexte c’est la seconde solution.</p>
<p>Mais ce n’est pas tout. En Haskell nous avons un raccourci de syntaxe, les « sections ». Tout opérateur binaire peut être « pré-appliqué ». Par exemple <code>(2+)</code> est équivalent à une fonction qui ajoute 2. De façon similaire, <code>(/pi)</code> est une fonction qui divise par pi. La position de l’argument à droite ou à gauche de l’opérateur ayant un sens, <code>(2^)</code> et <code>(^2)</code> représentant respectivement une puissance de deux et le carré. Mais que signifie <code>(-1)</code>? Est-ce l’opérateur <code>(-)</code> pré appliqué avec <code>1</code> en argument droit, ou est-ce le nombre négatif <code>-1</code> ?</p>
<p>Avant GHC 9.0, <code>(-1)</code> était de façon inconditionnelle équivalent au nombre négatif <code>-1</code>. Depuis GHC 9.0, et avec l’extension <code>LexicalNegation</code>, <code>(-1)</code> représente le nombre négatif, et <code>(- 1)</code> (notez l’espace), représente la fonction qui soustrait un.</p>
<p>C’est un changement qui peut sembler anodin et tout simple, mais il est assez évocateur de la difficulté d’avoir une syntaxe avancée et cohérente.</p>
<h2 id="toc-lineartypes">LinearTypes</h2>
<p>Les types linéaires, ou plutôt les fonctions linéaires, sont un nouvel outil du système de type qui permet d’exprimer de nouvelles contraintes. Dans cette section je me contenterai d’exemple « haut niveau » d’utilisation, sans entrer dans les détails de syntaxe et d’usage. Les curieux peuvent aller consulter la <a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0111-linear-types.rst">Proposal sur les types linéaires</a> qui contient de nombreux détails.</p>
<p>De façon simplifiée, une fonction linéaire garantit que ses arguments sont utilisés une fois. Ils ne peuvent pas être utilisés plus d’une fois et ils ne peuvent pas ne pas être utilisés.</p>
<p>Détaillons avec un exemple :</p>
<pre><code class="haskell"><span class="nf">titi</span> <span class="n">x</span> <span class="n">y</span> <span class="ow">=</span> <span class="kr">let</span>
<span class="n">a</span> <span class="ow">=</span> <span class="n">foo</span> <span class="n">x</span>
<span class="n">b</span> <span class="ow">=</span> <span class="n">biz</span> <span class="n">x</span>
<span class="kr">in</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span></code></pre>
<p>Dans cette fonction:</p>
<ul>
<li>
<code>x</code> est utilisé deux fois</li>
<li>
<code>y</code> n’est pas utilisé.</li>
<li>
<code>a</code> et <code>b</code> sont utilisés une unique fois.</li>
</ul>
<p>Ainsi, <code>a</code> et <code>b</code> peuvent satisfaire les contraintes de fonctions linéaires.</p>
<p>GHC 9.0 permet d’exprimer ce type de contrainte dans le système de type et d’ainsi empêcher la compilation si le code écrit ne respecte pas ces contraintes. Par exemple, comparons les fonctions suivantes :</p>
<pre><code class="haskell"><span class="nf">identity</span> <span class="ow">::</span> <span class="n">a</span> <span class="ow">-></span> <span class="n">a</span>
<span class="nf">identity</span> <span class="n">v</span> <span class="ow">=</span> <span class="n">v</span>
<span class="nf">identityLinear</span> <span class="ow">::</span> <span class="n">a</span> <span class="o">%</span><span class="mi">1</span><span class="ow">-></span> <span class="n">a</span>
<span class="nf">identityLinear</span> <span class="n">v</span> <span class="ow">=</span> <span class="n">v</span></code></pre>
<p><code>identityLinear</code> est la version « linéaire » de <code>identity</code>. Observez comment la syntaxe de la flèche <code>-></code> change pour devenir <code>%1-></code>.</p>
<p>Ces contraintes sont particulièrement utiles pour de la gestion de ressources. En s’assurant qu’un objet est forcément utilisé une fois, on peut s’assurer qu’une ressource sera correctement détruite. De plus on peut garantir qu’il n’y a pas plusieurs utilisations de la ressource.</p>
<p>D’ailleurs, le système de type de rust utilise une méthode proche des types linéaires pour suivre la durée de vie de ses variables et fournir des garanties similaires. Savoir qu’un objet ne sera jamais réutilisé peut aussi servir à des fins d’optimisation, en réutilisant par exemple les ressources allouées par celui-ci. C’est un processus similaire qui est utilisée en C++ avec la <em>move semantic</em>.</p>
<p>Une dépêche en cours de rédaction se concentre uniquement sur un exemple d’utilisation des types linéaires pour la création d’une bibliothèque robuste de gestion de socket, basé sur l’exemple de <a href="https://www.tweag.io/blog/2017-08-03-linear-typestates/">Tweag sur les types linéaires et les sockets</a>.</p>
<h2 id="toc-qualifieddo">QualifiedDo</h2>
<p>La syntaxe <code>do</code> en Haskell permet d’ordonner des calculs et d’introduire une relation de dépendance. L’exemple le plus abordable concerne la réalisation d’entrées sorties :</p>
<pre><code class="haskell"><span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"Quel est votre prénom ?"</span>
<span class="n">prénom</span> <span class="ow"><-</span> <span class="n">getString</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">prénom</span><span class="p">)</span></code></pre>
<p>Ici, la syntaxe de <code>do</code> permet d’exprimer que ces opérations seront réalisées dans l’ordre et met en évidence la dépendance entre ces instructions. <code>do</code> n’est en fait qu’un <em>sucre syntaxique</em> au-dessus des opérateurs <code>(>>)</code> et <code>(>>=)</code>, le code suivant étant parfaitement équivalent au bloc précédant :</p>
<pre><code class="haskell"><span class="nf">putStrLn</span> <span class="s">"Quel est votre prénom ?"</span>
<span class="o">>></span> <span class="p">(</span>
<span class="n">getString</span> <span class="o">>>=</span> <span class="p">(</span><span class="nf">\</span><span class="n">prénom</span> <span class="ow">-></span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">prénom</span><span class="p">))</span></code></pre>
<p>La syntaxe <code>do</code> est cependant générique et s’adapte à nombreuses opérations pour qui l’ordre et la notion de dépendance sont importants, tels que des listes, des calculs pouvant échouer, des parseurs, des calculs parallèles, des entrées sorties, de la gestion de configuration, du logging… <code>do</code> s’adapte en fait à tous les types qui sont instances (i.e. implémente l’interface) de <code>Monad</code>.</p>
<p>Cependant, l’interface de <code>Monad</code> peut être trop restrictive. Il existe de nombreuses variations autour de celles-ci, comme les monades linéaires, qui sont utilisées en conjugaison avec les types linéaires décrits plus haut.</p>
<p>Pour ces cas, l’extension <a href="https://ocharles.org.uk/guest-posts/2014-12-06-rebindable-syntax.html"><code>RebindableSyntax</code></a> permet de redéfinir le fonctionnement de do. Mais cette extension est peu pratique, implique de nombreux effets non désirés et est trop globale puisqu’elle entraîne une surcharge de toute la syntaxe d’Haskell.</p>
<p>GHC 9.0 apporte le support pour <code>QualifiedDo</code> qui permet de qualifier l’opérateur <code>do</code> et d’utiliser une définition des opérateurs <code>(>>=)</code> et <code>(>>)</code> présente dans un module. Au lieu d’écrire <code>do</code>, on peut écrire <code>M.do</code> où <code>M</code> est un module qui contient les définitions nécessaires à la surcharge de <code>do</code>.</p>
<p>Cette approche, contrairement a <code>RebindableSyntax</code> est limitée au <code>do</code> et bien plus pratique. Nécessaire avec l’arrivée des types linéaires qui en abusent, on peut imaginer que cette extension va démocratiser l’utilisation de monad exotiques.</p>
<h2 id="toc-haskell-language-server">Haskell Language Server</h2>
<p><a href="https://github.com/haskell/haskell-language-server">Haskell Language Server</a> est une implémentation du Language Server Protocol pour Haskell.</p>
<p>Il y a eu beaucoup de changements dans ce domaine depuis quelques mois et la sortie récente du HLS 1.0.0.0 nous permet maintenant de profiter d’une intégration d’Haskell avec les éditeurs de texte qui n’a plus à rougir vis-à-vis d’autres langages.</p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>GHC évolue, et c’est bien, pour qui s’intéresse à Haskell. Il est cependant assez difficile de vendre bon nombre de changements car ceux-ci sont souvent internes (comme le nouveau GC, des changements dans la génération de code…), ou alors touchent des points très avancés du système de type. Certains changements comme les types linéaires ne peuvent pas être résumés en quelques mots dans une dépêche.</p>
<p>Ce que j’attends du futur d’Haskell, et donc de GHC, se situe principalement au niveau de l’environnement, des outils. Je suis très satisfait de voir les évolutions actuelles sur le Haskell langage serveur. J’attends dans les années à venir un travail sur le debug et les performances. Voir à ce sujet le travail de Well-Typed sur un <a href="https://www.well-typed.com/blog/2021/01/fragmentation-deeper-look/">debugueur d’allocation mémoire</a>.</p>
<p><a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0380-ghc2021.rst">GHC 2021</a> pourrait voir le jour sous la forme d’une liste d’extensions de GHC permettant ainsi de réduire la longue (très longue) liste d’extensions à activer en haut de chaque fichier. La <a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0282-record-dot-syntax.rst">Record Dot Syntax</a> pourrait permettre de rendre le langage plus accessible en proposant une solution à une discussion longue comme le langage concernant l’usage des records.</p>
<p>GHC 9.2 est prévu pour « bientôt », les <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/9.2.1-notes.html">notes de version présumées</a> sont déjà disponibles.</p>
<p>Cette dépêche est loin d’être complète, alors n’hésitez pas à utiliser l’espace commentaires pour demander / apporter des précisions, comparer à votre langage préféré ou tout simplement dire bonjour.</p>
</div><div><a href="https://linuxfr.org/news/ghc-8-8-8-10-et-9-0.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117431/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/ghc-8-8-8-10-et-9-0#comments">ouvrir dans le navigateur</a>
</p>
GuillaumYves BourguignonnokomprendoAnthony JaguenaudBenoît SibaudSnarktisaacenikarBAudNils Ratusznikpalm123olivierwebhttps://linuxfr.org/nodes/117431/comments.atomtag:linuxfr.org,2005:Diary/394422020-11-13T15:51:50+01:002020-11-13T15:51:50+01:00Retour d'expérience sur les langages de programmationLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-petit-tour-dexp%C3%A9rience-sur-des-langages">Petit tour d'expérience sur des langages</a><ul>
<li><a href="#toc-ocaml">OCaml</a></li>
<li><a href="#toc-haskell">Haskell</a></li>
<li><a href="#toc-tcl-perl-python-raku">Tcl, Perl, Python, Raku</a></li>
<li><a href="#toc-common-lisp-racket">Common Lisp, Racket</a></li>
<li><a href="#toc-j">J</a></li>
<li><a href="#toc-coq">Coq</a></li>
<li><a href="#toc-go">Go</a></li>
<li><a href="#toc-rust">Rust</a></li>
</ul>
</li>
<li><a href="#toc-ce-quil-mest-rest%C3%A9-de-tout-%C3%A7a">Ce qu'il m'est resté de tout ça</a></li>
<li><a href="#toc-langages-que-jaimerais-creuser-un-peu-un-jour">Langages que j'aimerais creuser un peu un jour</a></li>
</ul>
<p>Ces derniers temps, j'apprends moins de langages nouveaux qu'il y a quelques années. Du coup, je me suis dit que c'était une occasion de faire le tour sur l'essentiel des langages que j'ai testés.</p>
<p>Dans ce journal, je fais un peu dans le classique du ceci ou cela m'a plu dans tel langage, telle autre chose ne m'a pas plu. Le tout est très subjectif, biaisé et reflète fortement les trucs que j'ai voulu faire avec ces langages. Mais bon, j'ai lu beaucoup d'articles de blog dans ce genre (enfin, en général sur un seul langage, ou L1 vs L2) et, même si ça n'aide pas souvent à découvrir le langage de nos rêves, ni à changer d'opinion ou à apprendre grand-chose sur un langage qu'on connait déjà, j'ai trouvé quand même ça souvent sympa à lire vite fait, même (voire surtout) quand mon ressenti est différent.</p>
<h2 id="toc-petit-tour-dexpérience-sur-des-langages">Petit tour d'expérience sur des langages</h2>
<h3 id="toc-ocaml">OCaml</h3>
<p><a href="https://fr.wikipedia.org/wiki/OCaml">OCaml</a> est le premier langage que j'ai appris ! (enfin, son prédécesseur Camllight initialement, le langage qui était utilisé qu'en prépas en France)</p>
<p>Les trucs que j'ai aimés :</p>
<ul>
<li>Compile vers du code natif assez efficace.</li>
<li>Typage expressif (<a href="https://fr.wikipedia.org/wiki/Type_alg%C3%A9brique_de_donn%C3%A9es">types algébriques</a>), mais pratique (<a href="https://fr.wikipedia.org/wiki/Inf%C3%A9rence_de_types">inférence de types</a>) et pas trop compliqué : langage abordable.</li>
<li>Mélange de code fonctionnel et impératif possible et plutôt facile.</li>
<li>Sympa pour manipuler des structures de données arborescentes. En particulier pour écrire des analyses ou transformations d'AST.</li>
<li>Documentation accessible en ligne de commande.</li>
</ul>
<p>Les trucs qui me laissent dubitatif :</p>
<ul>
<li>Des messages d'erreur qui se sont améliorés mais, le typage riche et l'inférence n'aidant pas, les erreurs ont toujours du mal à parler la langue des mortels.</li>
<li>Une syntaxe et un système de types pas trop compliqués, mais qui se compliquent ces dernières années : introduction des GADT (une sorte de types dépendants — en gros, des monstres surpuissants invoqués par des super héros) et les extensions de syntaxe ppx qui peuvent casser à chaque changement de version, entres autres; ça a du bon quand même.</li>
<li>La syntaxe : l'extension <a href="https://reasonml.github.io/">Reason</a> fait plus de modifications que strictement nécessaire, mais marquer clairement la fin des <em>pattern matching</em> et autres structures de contrôle (comme en Rust), ce serait déjà bien (après, des accolades ou un <code>end</code> comme en Coq ou Ruby, c'est du détail).</li>
<li>Pas besoin de préciser les bibliothèques utilisées en préambule de fichier.</li>
<li>Un nombre ok de bibliothèques tierces.</li>
</ul>
<p>Les trucs que j'ai moins aimés :</p>
<ul>
<li>Bibliothèque standard limitée, beaucoup de variantes de fonctions de base, mais peu au-delà (pas de compression, encodage, unicode, http). Au moins deux bibliothèques alternatives existent, mais elles résolvent surtout des soucis différents.</li>
<li>Exceptions, en particulier leur sur-utilisation dans la bibliothèque standard qui a conduit à l'introduction de variantes en <code>*_opt</code> renvoyant plutôt un type option, du genre <code>None</code> ou <code>Some x</code>, plutôt que <code>Not_found</code> (mais pas pour toutes les fonctions encore).</li>
<li>Manque de structures de contrôle impératives : pas de break, continue, return ; ça peut vite devenir gênant si on manipule beaucoup les tableaux (tableaux qui d'ailleurs gagneraient en ergonomie à être dynamiques).</li>
<li>Des fonctions non <a href="https://fr.wikipedia.org/wiki/R%C3%A9cursion_terminale">récursives terminales</a> (donc risque de débordement de pile) dans la bibliothèque standard qui ont conduit à plus de duplication avec l'introduction de fonctions récursives terminales équivalentes.</li>
<li>Les bibliothèques, à moins d'être très populaires, risquent d'être mal documentées : les types des fonctions, si on a de la chance une courte description pour chacune, parfois un exemple dans le README.</li>
<li>Certaines bibliothèques connues font dans l'ingénierie lourde (comme le framework <a href="https://ocsigen.org/home/intro.html">ocsigen</a>), pas toujours évident de trouver des alternatives plus simples et bien documentées.</li>
</ul>
<h3 id="toc-haskell">Haskell</h3>
<p><a href="https://fr.wikipedia.org/wiki/Haskell">Haskell</a> a des propriétés similaires à OCaml, à ceci près qu'il accueille avec joie la complexité. Plus amusant, mais plus frustrant aussi.</p>
<p>Les trucs que j'ai aimés :</p>
<ul>
<li>Compile vers du code natif assez efficace.</li>
<li>Typage expressif, inférence de type.</li>
<li>Comme OCaml, pratique pour la manipulation d'AST.</li>
<li>La bibliothèque [parsec](<a href="https://en.wikipedia.org/wiki/Parsec_(parser)">https://en.wikipedia.org/wiki/Parsec_(parser)</a> qui permet de parser en combinant des parseurs. Des alternatives dans d'autres langages ont vu le jour, mais parsec reste plus naturel (mais pas le plus performant par contre).</li>
</ul>
<p>Les trucs qui me laissent dubitatif :</p>
<ul>
<li>Les <a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)">monades</a>, des abstractions qui permettent de structurer les programmes de façon générique. C'est utilisé dans parsec pour combiner naturellement des parseurs, par exemple. Les monades IO et ST permettent de faire de l'impératif de façon compliquée aussi. C'est aussi utilisé pour rendre certains tutoriels très abstraits.</li>
<li>Un système de types plus complexe que celui d'OCaml et qui rencontre plus tôt les limites de l'inférence. Et une pléthore d'extensions de langage optionnelles.</li>
<li>Des messages d'erreur pour initiés à cause du typage expressif et de l'inférence de types.</li>
<li>Une communauté intéressée par des concepts comme les monades, les flèches, les catégories, etc. Ça se reflète dans de nombreux tutoriels et échanges, tout comme dans les bibliothèques tierces. C'est plus dur de trouver des contenus qui font dans le pragmatique. Ce point devient positif si on est passionné par les concepts mentionnés, ou source de frustration autrement :-)</li>
<li>Je n'aime pas trop certains éléments de syntaxe : l'indentation significative, l'abondance d'opérateurs avec priorités et associativité variables.</li>
<li>Des préambules de fichier avec souvent une suite interminable d'imports de bibliothèques et un mélange d'imports avec noms qualifiés et non qualifiés.</li>
</ul>
<p>Les trucs que j'ai moins aimés :</p>
<ul>
<li>Compilation lente.</li>
<li>Possible mais difficile de faire de l'impératif : manipuler des tableaux est tout sauf agréable (par exemple pour représenter la carte dans un jeu, faire de la recherche de chemins, etc.).</li>
<li>Il faut utiliser une bibliothèque externe pour avoir des chaînes de caractères implémentées raisonnablement.</li>
<li>Beaucoup de bibliothèques, mais c'est pas facile de s'y retrouver.</li>
<li>Beaucoup de bibliothèques font dans l'ingénierie lourde.</li>
<li>Beaucoup de bibliothèques ont un arbre conséquent de dépendances. </li>
<li>Beaucoup de bibliothèques sont mal documentées.</li>
</ul>
<p>Exemple personnel : recherche d'une bibliothèque pour gérer le xml. Première tentative, <a href="https://hackage.haskell.org/package/hxt">hxt</a> : pas moyen de trouver un indice dans la doc sur comment commencer (le théoricien remarquera que ça s'inspire de la <a href="https://fr.wikipedia.org/wiki/Morphisme">théorie des flèches</a>, mais ça l'aidera pas forcément tant que ça non plus). Deuxième tentative, <a href="https://hackage.haskell.org/package/HaXml-1.25.5/docs/Text-XML-HaXml.html">HaXml</a> : un peu moins abstrait peut-être, mais bon courage quand même. Troisième tentative, <a href="https://hackage.haskell.org/package/xml-1.3.14/docs/Text-XML-Light.html">Text-XML-Light</a>, le nom semble prometteur : pas d'exemples, mais ça semble en effet plus simple. Si l'on n'a pas encore capitulé, c'est le moment de chercher s'il n'y a pas un tutoriel à peu près à jour quelque part dans le wiki du langage pour une de ces bibliothèques.</p>
<p>Ceci dit, Haskell, c'est vraiment l'occasion de découvrir des concepts théoriques en faisant des trucs concrets, du genre découvrir à l'aide d'un framework web (appelé snap si ma mémoire est bonne) que les lentilles c'est pas seulement un truc qui se mange.</p>
<h3 id="toc-tcl-perl-python-raku">Tcl, Perl, Python, Raku</h3>
<p>Tous ces langages se ressemblent un peu : typage dynamique, bases faciles à apprendre, plus ou moins d'OO, communauté pragmatique avec des écosystèmes de packages très variés, langages pas super performants mais suffisamment dans beaucoup de cas. Du coup, je vais parler uniquement des choses marquantes qui m'ont semblé uniques à chacun.</p>
<p>Pour Perl :</p>
<ul>
<li>Intégration des expressions régulières dans le langage, inspirée de <a href="https://fr.wikipedia.org/wiki/Stream_Editor">Sed</a> : erreurs dans la regexp à la compilation, plein de fonctionnalités sur l'Unicode.</li>
<li>Mode de traitement de texte inspiré de <a href="https://fr.wikipedia.org/wiki/Awk">Awk</a> et adapté aux traitements rapides en ligne de commande.</li>
<li>Une documentation commode en ligne de commande et qui permet de démarrer vite, avec beaucoup d'exemples dans un style un peu « recettes » en synopsis.</li>
<li>Quelques incantations répétitives à écrire en début de chaque fichier.</li>
<li>Un peu plus fonctionnel (fonctions anonymes, portée lexicale des variables).</li>
<li>Mini typage statique partiel (scalaires vs tableaux vs tables de hachage, typos dans les noms de variables attrapées lors de la compilation).</li>
</ul>
<p>Pour Python :</p>
<ul>
<li>Beaucoup de bibliothèques dans le domaine du calcul scientifique (numpy, etc.).</li>
<li>Documentation plus OO que celle de Perl, plus orientée web que ligne de commande.</li>
<li>
<a href="https://fr.wikipedia.org/wiki/Liste_en_compr%C3%A9hension">Listes en
compréhension</a>
(perso, j'aime pas trop, ça se démarque un peu du reste du langage).</li>
</ul>
<p>Pour <a href="https://fr.wikipedia.org/wiki/Tool_Command_Language">Tcl</a> :</p>
<ul>
<li>Syntaxe où « tout est chaîne de caractères et commandes », mais fait proprement et sans pièges, contrairement au shell. Ça permet de faire des DSLs très naturels.</li>
<li>Par exemple, l'intégration très sympa avec SQLite : on peut écrire <code>db eval {SELECT uid FROM table WHERE n <= $max AND time < $epoch}</code> en mettant directement les variables <code>$max</code> et <code>$epoch</code> dans la requête sans risquer d'injections SQL (c'est pas de l'interpolation en fait). Ça évite la typique redondance où il faut passer les arguments à la requête après, souvent avec le même nom.</li>
<li>Plus fragile aux typos que Perl ou Python.</li>
<li>Intégration très naturelle avec Tk : mon langage préféré pour les petits GUI couplé à SQLite.</li>
<li>Documentation sous forme de pages de manuel proches de celles des outils en ligne de commande : plus formelle que la documentation Perl.</li>
<li>Wiki communautaire plein d'exemples, mais un peu chaotique.</li>
<li>Écosystème plus petit que les autres : pas idéal pour faire du calcul scientifique, par exemple, et moins de choix en général (par exemple pour faire du web).</li>
<li>Malgré son caractère de langage généraliste et bibliothèque standard assez vaste, Tcl peut être aussi facilement utilisé comme langage d'extension d'un programme en C (à la Lua).</li>
</ul>
<p>Pour <a href="https://fr.wikipedia.org/wiki/Raku_(langage)">Raku</a> (anciennement Perl 6) :</p>
<ul>
<li>Langage généraliste à tout faire très (trop ?) ambitieux et pas effrayé par la complexité.</li>
<li>Langage plutôt cohérent et orthogonal, inspiré de Perl (mais aussi Ruby et d'autres), mais plus OO dans l'esprit.</li>
<li>Les messages d'erreur sont plutôt sympas.</li>
<li>Les expressions régulières sont intégrées dans un concept plus vaste de grammaires, très pratique pour écrire des parseurs.</li>
<li>La VM se lance un peu lentement et les modules compilent pas vite non plus.</li>
<li>Les expressions régulières, qui sont quand même fondamentales dans ce langage, étaient encore très mal optimisées il y a un ou deux ans, la dernière fois que j'ai testé.</li>
<li>L'écosystème est assez jeune encore.</li>
</ul>
<h3 id="toc-common-lisp-racket">Common Lisp, Racket</h3>
<p><a href="https://fr.wikipedia.org/wiki/Common_Lisp">Common Lisp</a> et <a href="https://fr.wikipedia.org/wiki/Racket_(langage)">Racket</a> sont des langages fonctionnels, par défaut au typage dynamique, ils se prêtent très bien à la manipulation de structures arborescentes et sont très prisés pour leur extensibilité à l'aide de systèmes de macros évolués. Les deux ont pas mal de bibliothèques tierces et compilent vers du code assez efficace (normalement moins que OCaml ou Haskell, mais nettement plus que Python ou Perl).</p>
<p>Pour Racket :</p>
<ul>
<li>Une documentation plus propre, surtout pour les bibliothèques tierces. Pour tout dire, lorsque j'ai testé, j'étais émerveillé par <a href="https://docs.racket-lang.org/scribble/index.html">scribble</a>, leur langage de documentation, qui est un dialecte de racket lui-même et permet de faire plein de validations sur la doc, dont le fait que les exemples compilent et renvoient le bon truc.</li>
<li>Plus orienté fonctionnel, mais aussi plus académique : une partie de l'objectif du langage est d'illustrer les recherches en théorie des langages extensibles.</li>
<li>Démarrage plus lent de la VM.</li>
</ul>
<p>Pour Common Lisp :</p>
<ul>
<li>Macros plus simples, mais non <a href="https://en.wikipedia.org/wiki/Hygienic_macro">hygiéniques</a> (ce qui est pas cool par les temps qui courent).</li>
<li>Un peu plus fonctionnel, en particulier la construction extrêmement flexible <code>loop</code>, ou peut-être encore mieux, la bibliothèque <a href="https://common-lisp.net/project/iterate/">iterate</a> : une macro d'itération très extensible !</li>
<li>Un peu le bazar pour ce qui est des bibliothèques tierces : le gestionnaire de paquets lui-même, bien que fonctionnel, est considéré bêta depuis très très longtemps.</li>
</ul>
<p>Si l'on veut juste apprendre afin de découvrir les macros pour faire des DSLs, c'est bien plus simple de faire ça avec Tcl.</p>
<h3 id="toc-j">J</h3>
<p><a href="https://fr.wikipedia.org/wiki/J_(langage)">J</a> est un langage fonctionnel de manipulation vectorisée de tableaux multi-dimensionnels avec une syntaxe compacte faisant usage de primitives de haut niveau. C'est une variante moderne d'<a href="https://fr.wikipedia.org/wiki/APL_(langage)">APL</a> avec une syntaxe ASCII et plus de fonctionnalités.</p>
<p>Les trucs que j'ai aimés :</p>
<ul>
<li>La notation compacte est sympa pour expérimenter dans l'invite de commande.</li>
<li>Les primitives du langage sont très génériques et flexibles.</li>
<li>C'est amusant et ça fait réfléchir différemment à certains problèmes : je me suis amusé par exemple avec les problèmes du project euler, la génération de cartes et algos de dijkstra, ou l'écriture d'un automate pour parser des poèmes.</li>
</ul>
<p>Les trucs que j'ai moins aimés :</p>
<ul>
<li>Lorsqu'un algorithme ne se prête pas bien à une vectorisation, ça devient un casse-tête infernal.</li>
<li>J'ai beaucoup de mal à lire le code écrit par les autres.</li>
<li>De manière générale, j'ai l'impression que ce langage a tendance à facilement faire saturer ma mémoire cognitive de travail : un langage idéal pour quand j'ai besoin de me sentir idiot, ça marche à chaque fois.</li>
<li>Pour tout le code non algorithmique d'un projet, c'est aussi verbeux que n'importe quel langage et on ressent l'absence de structs/maps.</li>
</ul>
<p>Le langage est surtout utilisé en statistiques et calcul scientifique, mais je dois dire que si j'avais un besoin dans ce domaine, je chercherais plutôt du côté de Python, <a href="https://fr.wikipedia.org/wiki/R_(langage_de_programmation_et_environnement_statistique)">R</a> ou <a href="https://fr.wikipedia.org/wiki/Julia_(langage)">Julia</a>. J'utilise J parfois comme calculatrice. En pratique je me contente souvent de la calculatrice <code>dc</code> du standard POSIX :-)</p>
<h3 id="toc-coq">Coq</h3>
<p><a href="https://coq.inria.fr/">Coq</a> est un assistant de preuve et un langage purement fonctionnel que j'ai pas mal utilisé pendant la thèse dans le domaine de la compilation. Je suis resté simple utilisateur, assez ignorant des théories derrière et des techniques avancées d'automatisation de preuve. Il y a eu <a href="//linuxfr.org/news/sortie-de-coq-8-5-beta-un-assistant-de-preuve-formelle">une dépêche</a> ici il y a quelques années par des gens qui connaissent bien mieux le truc (perso, j'avais juste contribué avec un exemple).</p>
<p>Les trucs que j'ai aimés :</p>
<ul>
<li>C'est rigolo. Sérieusement, écrire des preuves de programme, c'est un peu comme un jeu, avec des moments de victoires épiques et de défaites accablantes.</li>
<li>C'est un langage avec un système de types extrêmement expressif : imaginez par exemple pouvoir écrire à l'aide du système de types qu'une passe d'optimisation d'un compilateur ne change pas la sémantique d'un programme et n'introduit donc pas de bugs inattendus !</li>
<li>Comme OCaml ou Haskell, le langage se prête bien à la manipulation d'AST et donc à l'écriture de compilateurs (avec des difficultés additionnelles ceci dit, comme le fait que les entiers sont représentés par un type algébrique et que Coq offre uniquement des structures de données purement fonctionnelles).</li>
</ul>
<p>Les trucs qui me laissent dubitatif :</p>
<ul>
<li>Écrire du code propre est relativement facile, mais des preuves propres, c'est une autre histoire : il y a l'approche où on essaie d'automatiser un maximum, ce qui demande de connaître très bien le langage de tactiques (donc preuve compréhensible par moins de monde), d'avoir une machine puissante (automatisation signifie plus de travail pour Coq) et compromettre la maintenabilité (du genre preuve qui passe plus avec la version suivante de Coq); il y a l'approche où on automatise pas trop et écrit beaucoup de lemmes intermédiaires et des preuves parfois répétitives, on insiste jusqu'à ce que ça passe à force de sentiments forts : je faisais partie des utilisateurs chevronnés de cette technique de jeu.</li>
</ul>
<p>Les trucs que j'ai moins aimés :</p>
<ul>
<li>Ça prend beaucoup de temps. Difficile de trouver des applications qui justifient cela, et ce même dans les domaines qui se prêtent assez bien à la preuve de programme (comme la compilation).</li>
<li>Il faut utiliser un autre langage, généralement OCaml, pour les parties non purement fonctionnelles du programme qui font de l'I/O.</li>
<li>C'est un langage complexe avec des messages d'erreur qui demandent une bonne expérience pour être appréhendés.</li>
<li>Faut pas s'attendre à trouver des contributeurs dans la nature : les programmeurs Coq se trouvent tous ou presque dans le domaine de la recherche.</li>
<li>Comme tout jeu, on finit par se lasser un peu à un moment et un jeu long dont on se lasse est un jeu qu'on ne finit pas (à moins d'être payé pour).</li>
<li>Les ressources disponibles dans la nature pour apprendre sont limitées, souvent écrites pour des gens qui font une thèse et sont intéressés par la théorie. La pratique et les astuces de preuve, faut les apprendre soi-même ou lors d'échanges avec les collègues si on a la chance d'être dans un environnement Coq. Bref, c'est peu accessible.</li>
</ul>
<h3 id="toc-go">Go</h3>
<p><a href="https://fr.wikipedia.org/wiki/Go_(langage)">Go</a> est un langage que j'utilise beaucoup ces derniers temps (frundis, jeux, des petits scripts), je suis plutôt satisfait.</p>
<p>Les trucs que j'ai aimés :</p>
<ul>
<li>Compile vers du code natif efficace. Compilation rapide, statique par défaut.</li>
<li>Langage : structures de contrôle impératives flexibles (for, switch, break, continue, labels de boucle), les essentiels du fonctionnel (fonctions de première classe et <a href="https://fr.wikipedia.org/wiki/Fermeture_(informatique)">clôtures lexicales</a>), l'essentiel de l'OO (structs, méthodes et interfaces, pas de classes), l'essentiel du typage statique (typage moyennement expressif, mais flexible au besoin et sans conversions implicites ni inférences trop génériques qui compliquent les messages d'erreur), l'essentiel des structures de données (maps et tableaux dynamiques, comme avec Perl, Python ou Ruby).</li>
<li>Une bibliothèque standard fournie, mais abordable et bien documentée.</li>
<li>Beaucoup de bibliothèques tierces bien documentées, souvent avec peu ou pas de dépendances.</li>
<li>Crosscompilation facile pour les programmes en pur Go (avec export en WebAssembly facile).</li>
<li>Programmation concurrente facile avec les channels et goroutines.</li>
<li>Un package, c'est tous les fichiers d'un dossier: pas besoin de faire un package différent pour éviter d'avoir trop de trucs dans un même fichier.</li>
<li>Documentation accessible en ligne de commande et, en général, langage pratique à utiliser dans un terminal avec plein d'outils (renommages, analyses statiques, bonne intégration vim/emacs, etc.).</li>
</ul>
<p>Les trucs qui me laissent dubitatif :</p>
<ul>
<li>URLs pour les noms d'import de package : ça conduit à devoir modifier le code si on change l'hébergement du projet. Ceci dit, le packaging n'a pas de solution magique non plus : j'ai beau ne pas vraiment aimer cette idée, c'est souvent pratique et pas clairement pire que les alternatives sur tous les points.</li>
<li>Absence de types génériques (<a href="https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md">en cours</a> d'être résolue, peut-être pour dans un an ou deux) : ça serait bien dans certains cas (bibliothèques génériques pour structures de données complexes ou opérations génériques sur des channels), mais ça me manque assez rarement tout compte fait (je ne ressens pas le besoin de remplacer les boucles <code>for</code> par des fonctions génériques, par exemple).</li>
<li>Plus verbeux qu'un langage dynamique, essentiellement du fait des signatures de fonctions (en pratique rentable dans un projet qui va au-delà du script, je trouve).</li>
</ul>
<p>Les trucs que j'ai moins aimés :</p>
<ul>
<li>Difficile parfois de faire du pur Go (GUI, SQLite, etc.) : l'avantage de la crosscompilation facile disparaît dans ce cas. C'est pas vraiment un point négatif, mais une annulation courante de point positif.</li>
</ul>
<h3 id="toc-rust">Rust</h3>
<p>Rust est un langage qui a pas mal de popularité en ce moment, <a href="//linuxfr.org/tags/rust/public">pas mal de trucs</a> sont passés sur linuxfr. J'ai lu un tutoriel, testé des exemples et lu de la doc, mais je n'ai jamais vraiment programmé avec, donc voici plutôt un retour d'apprentissage et d'utilisation :</p>
<ul>
<li>Des programmes très performants, dont le génial <a href="https://lib.rs/crates/ripgrep">ripgrep</a> qui remplace avantageusement grep.</li>
<li>Des programmes avec beaucoup de dépendances et qui mettent beaucoup de temps à compiler.</li>
<li>Langage d'inspirations multiples avec typage assez expressif (types somme et filtrage par motif similaires à OCaml), des <a href="https://en.wikipedia.org/wiki/Trait_(computer_programming)"><em>traits</em></a> (mais sans classes, un peu comme en Go).</li>
<li>Langage qui facilite l'impératif et le fonctionnel, même si l'absence de GC rend certaines pratiques de programmation fonctionnelle (comme une fonction qui renvoie une fonction) un peu alambiquées à écrire.</li>
<li>Un peu complexe à apprendre du fait de quelques notions assez subtiles (<a href="https://fr.wikipedia.org/wiki/Rust_(langage)#Possession_et_emprunt"><em>ownership</em>, <em>borrowing</em></a>) qui facilitent l'écriture de programmes concurrents <em>memory safe</em>, et du fait de l'ampleur du langage (macros, etc.).</li>
<li>Une documentation orientée web (même s'il me semble que j'avais trouvé un outil non officiel en ligne de commande).</li>
</ul>
<p>J'aimerais m'y mettre un jour, mais j'ai pas d'idée de projet personnel qui profite de l'absence de GC : un peu comme pour le C et le C++, avec la différence qu'avec ceux-ci je me suis déjà retrouvé à devoir lire voire modifier du code dans les programmes que j'utilise, et ça ne m'est pas encore arrivé avec du Rust.</p>
<h2 id="toc-ce-quil-mest-resté-de-tout-ça">Ce qu'il m'est resté de tout ça</h2>
<p>Au final, aujourd'hui, les seuls langages que j'utilise vraiment encore sont Go (pour un peu tout), Tcl (pour les GUIs et SQLite) et Perl (pour les petits scripts et <a href="https://fr.wikipedia.org/wiki/Comprehensive_Perl_Archive_Network">CPAN</a>). C'est sans compter des petits bouts de Javascript (dont j'ai pas parlé, car j'ai juste écrit des petits trucs en vanilla avec la doc de mozilla, sans aller chercher quoi que ce soit dans l'écosystème), ou les modifs de code C/C++ pour compiler sous OpenBSD, et mes tentatives le plus souvent couronnées d'échec pour compiler puis lancer du Java (dernière défaite cuisante en date : le jeu Mindustry qui est passé en dépêche il y a peu).</p>
<p>Ceci dit, même si au final on peut se dire à quoi bon avoir exploré autant de langages, j'ai bon souvenir de tout ça et ça influe probablement sur ma façon de programmer, j'espère qu'en bien :-)</p>
<h2 id="toc-langages-que-jaimerais-creuser-un-peu-un-jour">Langages que j'aimerais creuser un peu un jour</h2>
<p>Un langage relativement nouveau qui m'a l'air intéressant est <a href="https://www.nongnu.org/txr/">txr</a> : c'est en fait la combinaison de deux langages, un langage qui permet de capturer des motifs et parser facilement des documents, inspiré d'Awk, et un langage au style Lisp, mais différent. C'est pas un petit langage !</p>
<p>Dans le domaine des langages logiques, je trouve curieux <a href="https://fr.wikipedia.org/wiki/Mercury_(langage)">Mercury</a>, qui est un langage inspiré de <a href="https://fr.wikipedia.org/wiki/Prolog">Prolog</a> pour la partie logique, et Haskell pour la partie typage.</p>
<p>Pour ce qui est des langages concaténatifs, inspirés de <a href="https://fr.wikipedia.org/wiki/Forth_(langage)">Forth</a>, <a href="https://fr.wikipedia.org/wiki/Factor">Factor</a> semble être une approche moderne intéressante. Ceci dit, mes quelques lectures de tutos me donnent l'impression que mon cerveau ne gère pas bien l'approche concaténative de pile dès que ça devient un peu complexe (un peu la même sensation qu'avec J, mais pas aussi marquée).</p>
<p>J'ai vu passer assez souvent des articles sur le langage assez jeune mais plutôt actif <a href="https://en.wikipedia.org/wiki/Zig_(programming_language)">Zig</a>. Je me demande comment il se ressent en pratique par rapport au C voire au Rust ou C++.</p>
<div><a href="https://linuxfr.org/users/anaseto/journaux/retour-d-experience-sur-les-langages-de-programmation.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122223/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/anaseto/journaux/retour-d-experience-sur-les-langages-de-programmation#comments">ouvrir dans le navigateur</a>
</p>
anasetohttps://linuxfr.org/nodes/122223/comments.atomtag:linuxfr.org,2005:Bookmark/10622020-01-03T18:44:59+01:002020-01-03T18:44:59+01:00The Simple Haskell Initiative<a href="https://www.simplehaskell.org/">https://www.simplehaskell.org/</a> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/119062/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/liens/the-simple-haskell-initiative#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/119062/comments.atomtag:linuxfr.org,2005:Diary/388562019-12-25T19:27:26+01:002019-12-29T13:18:46+01:00Configurer VSCode pour Haskell (Debian/Nix/NixOS)Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-aper%C3%A7u-des-outils">Aperçu des outils</a></li>
<li><a href="#toc-installation-avec-debian">Installation avec Debian</a></li>
<li><a href="#toc-debiannix">Debian + Nix</a></li>
<li><a href="#toc-nixos--homemanager">NixOS + home‑manager</a></li>
<li><a href="#toc-tester-linstallation">Tester l’installation</a></li>
</ul>
<p>Comme beaucoup de langages de programmation, Haskell n’a pas d’environnement de développement officiel ni même consensuel. Cependant, il existe différentes configurations classiques : Emacs + Intero/Dante, Vim + Ghcid, IntelliJ-Haskell…</p>
<p>Depuis quelque temps, l’éditeur de texte Visual Studio Code propose un environnement intéressant pour développer en Haskell, notamment couplé à HIE et à Stack. Cette configuration apporte les principales fonctionnalités d’un IDE : coloration syntaxique, navigation de code, compilation, documentation, auto‑complétion… Cependant, l’installation de ces outils n’est pas complètement triviale. Ce journal explique comment la réaliser avec Debian, avec Debian + Nix et avec NixOS (voir également la <a href="https://youtu.be/xYWEJ3eLxFE">vidéo YouTube</a>).</p>
<h2 id="toc-aperçu-des-outils">Aperçu des outils</h2>
<p><a href="https://code.visualstudio.com/">Visual Studio Code</a> est un éditeur de texte extensible, supporté par Microsoft. VSCode est un projet <em>open source</em>, mais on installe généralement un binaire non libre (<a href="https://vscodium.com/">VS Codium</a> fournit des binaires sous licence libre, selon le même principe que Chrome/Chromium).</p>
<p>Le <a href="https://microsoft.github.io/language-server-protocol/overview"><em>Language Server Protocol</em> (LSP)</a>, également supporté par Microsoft, est un protocole permettant de réaliser plus facilement des outils de développement (navigation de code, auto‑complétion…).</p>
<p><a href="https://github.com/haskell/haskell-ide-engine"><em>Haskell IDE Engine</em> (HIE)</a> est un <em>back‑end</em> implémentant LSP pour le langage Haskell.</p>
<p><a href="https://github.com/alanz/vscode-hie-server"><em>Haskell Language Server Client</em></a> est une extension VSCode permettant d’utiliser HIE.</p>
<p>Enfin, <a href="https://docs.haskellstack.org/en/stable/README/">Stack</a> est un outil permettant de définir et de construire un projet Haskell. Stack permet de faire fonctionner HIE et est utilisable depuis VSCode.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6769746c61622e696f2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f34322f696d616765732f7673636f64652e706e67/vscode.png" alt="VSCode" title="Source : https://nokomprendo.gitlab.io/posts/tuto_fonctionnel_42/images/vscode.png"></p>
<h2 id="toc-installation-avec-debian">Installation avec Debian</h2>
<p>L’installation sous Debian de la <em>toolchain</em> « VSCode + HIE + client HIE + Stack » est assez pénible. Ces outils ne sont pas fournis par la logithèque (ou alors dans des versions obsolètes), il faut donc les installer « à la main » et parfois même les compiler (pour HIE, prévoir 4 Gio de mémoire vive et 1 h de compilation).</p>
<ul>
<li>
<p>installer les dépendances :</p>
<pre><code class="sh">sudo apt install libicu-dev libncurses-dev libgmp-dev libtinfo-dev</code></pre>
</li>
<li>
<p>installer Stack :</p>
<pre><code class="sh">curl -sSL https://get.haskellstack.org/ <span class="p">|</span> sh</code></pre>
</li>
<li>
<p><a href="https://code.visualstudio.com/docs/setup/linux">télécharger VSCode</a> et l’installer :</p>
<pre><code class="sh">sudo apt install ./<file>.deb</code></pre>
</li>
<li>
<p>compiler et installer HIE :</p>
<pre><code class="sh">git clone https://github.com/haskell/haskell-ide-engine --recurse-submodules
<span class="nb">cd</span> haskell-ide-engine
./install.hs build</code></pre>
</li>
<li><p>lancer VSCode et installer l’extension <code>Haskell Language Server</code> ;</p></li>
<li><p>configurer l’extension ou le <code>PATH</code> (avec <code>$HOME/.local/bin</code> et <code>$(stack path --compiler-bin)</code>).</p></li>
</ul>
<p>Et avec un peu de chance, la toolchain fonctionne. Personnellement, je n'ai pas réussi à la faire fonctionner complètement…</p>
<h2 id="toc-debiannix">Debian + Nix</h2>
<p><a href="https://nixos.org/nix/">Nix</a> est un gestionnaire de paquets utilisable sur les distributions GNU/Linux. Nix permet également d’utiliser le système de cache <a href="https://cachix.org/">Cachix</a>, ce qui permet d’installer notre <em>toolchain</em> Haskell/VSCode en quelques minutes.</p>
<ul>
<li>
<p>installer les dépendances Debian :</p>
<pre><code class="sh">sudo apt install curl rsync build-essential libgmp-dev</code></pre>
</li>
<li>
<p>installer et configurer Nix :</p>
<pre><code class="sh">curl https://nixos.org/nix/install <span class="p">|</span> sh
nix-channel --add https://nixos.org/channels/nixos-19.09 nixpkgs
nix-channel --update
<span class="nb">echo</span> <span class="s2">". </span><span class="nv">$HOME</span><span class="s2">/.nix-profile/etc/profile.d/nix.sh"</span> >> ~/.bashrc
<span class="nb">source</span> ~/.bashrc</code></pre>
</li>
<li>
<p>autoriser les logiciels non libres, dans <code>~/.config/nixpkgs/config.nix</code> :</p>
<pre><code class="nix"><span class="p">{</span> <span class="ss">allowUnfree =</span> <span class="no">true</span><span class="p">;</span> <span class="p">}</span></code></pre>
</li>
<li>
<p>installer cachix et activer le <a href="https://github.com/Infinisil/all-hies">cache pour HIE</a> :</p>
<pre><code class="sh">nix-env -iA nixpkgs.cachix
cachix use all-hies</code></pre>
</li>
<li>
<p>ajouter un <em>overlay</em> pour HIE, dans <code>~/.config/nixpkgs/overlays/hies.nix</code> :</p>
<pre><code class="nix">self<span class="p">:</span> super<span class="p">:</span>
<span class="k">let</span>
<span class="ss">all-hies =</span> <span class="nb">import</span> <span class="p">(</span>fetchTarball <span class="s2">"https://github.com/infinisil/all-hies/tarball/master"</span><span class="p">)</span> <span class="p">{};</span>
<span class="k">in</span> <span class="p">{</span>
<span class="ss">hies =</span> all-hies<span class="o">.</span>selection <span class="p">{</span> <span class="ss">selector =</span> p<span class="p">:</span> <span class="p">{</span> <span class="k">inherit</span> <span class="p">(</span>p<span class="p">)</span> ghc865 ghc844<span class="p">;</span> <span class="p">};</span> <span class="p">};</span>
<span class="p">}</span></code></pre>
</li>
<li>
<p>installer Stack, VSCode et HIE :</p>
<pre><code class="sh">nix-env -iA nixpkgs.stack nixpkgs.vscode nixpkgs.hies</code></pre>
</li>
<li><p>lancer VSCode et installer l’extension <code>Haskell Language Server</code>.</p></li>
</ul>
<p>Et normalement la <em>toolchain</em> fonctionne.</p>
<h2 id="toc-nixos--homemanager">NixOS + home‑manager</h2>
<p><a href="https://nixos.org/nixos/">NixOS</a> est une distribution GNU/Linux basée sur Nix. <a href="https://github.com/rycee/home-manager">Home‑manager</a> est un système de gestion de configuration utilisateur basé sur Nix (voir l’<a href="//linuxfr.org/news/gestion-de-paquets-evoluee-avec-nix-cachix-overlays-et-home-manager">article sur home‑manager</a>).</p>
<ul>
<li>
<p>autoriser les logiciels non libres, dans <code>~/.config/nixpkgs/config.nix</code> :</p>
<pre><code class="nix"><span class="p">{</span> <span class="ss">allowUnfree =</span> <span class="no">true</span><span class="p">;</span> <span class="p">}</span></code></pre>
</li>
<li>
<p>installer cachix et activer le <a href="https://github.com/Infinisil/all-hies">cache pour HIE</a> :</p>
<pre><code class="sh">nix-env -iA nixpkgs.cachix
cachix use all-hies</code></pre>
</li>
<li>
<p>ajouter un <em>overlay</em> pour HIE, dans <code>~/.config/nixpkgs/overlays/hies.nix</code> :</p>
<pre><code class="nix">self<span class="p">:</span> super<span class="p">:</span>
<span class="k">let</span>
<span class="ss">all-hies =</span> <span class="nb">import</span> <span class="p">(</span>fetchTarball <span class="s2">"https://github.com/infinisil/all-hies/tarball/master"</span><span class="p">)</span> <span class="p">{};</span>
<span class="k">in</span> <span class="p">{</span>
<span class="ss">hies =</span> all-hies<span class="o">.</span>selection <span class="p">{</span> <span class="ss">selector =</span> p<span class="p">:</span> <span class="p">{</span> <span class="k">inherit</span> <span class="p">(</span>p<span class="p">)</span> ghc865 ghc844<span class="p">;</span> <span class="p">};</span> <span class="p">};</span>
<span class="p">}</span></code></pre>
</li>
<li>
<p>activer VSCode + HIE, dans <code>~/.config/nixpkgs/home.nix</code> :</p>
<pre><code class="nix"> programs<span class="o">.</span><span class="ss">vscode =</span> <span class="p">{</span>
<span class="ss">enable =</span> <span class="no">true</span><span class="p">;</span>
<span class="ss">haskell =</span> <span class="p">{</span>
<span class="ss">enable =</span> <span class="no">true</span><span class="p">;</span>
<span class="ss">hie =</span> <span class="p">{</span>
<span class="ss">enable =</span> <span class="no">true</span><span class="p">;</span>
<span class="ss">executablePath =</span> <span class="s2">"</span><span class="si">${</span>pkgs<span class="o">.</span>hies<span class="si">}</span><span class="s2">/bin/hie-wrapper"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">};</span></code></pre>
</li>
<li>
<p>mettre à jour la configuration utilisateur :</p>
<pre><code class="sh">home-manager switch</code></pre>
</li>
</ul>
<p>Et c’est tout. L’intégration de HIE dans VSCode est faite automatiquement.</p>
<h2 id="toc-tester-linstallation">Tester l’installation</h2>
<ul>
<li>
<p>créer un projet Haskell et lancer VSCode :</p>
<pre><code class="sh">stack new myproject --resolver<span class="o">=</span>lts-14
<span class="nb">cd</span> myproject
code .</code></pre>
</li>
<li><p>une fois VSCode lancé, vérifier la navigation de code, l’auto‑complétion, la compilation avec Stack dans la console VSCode ;</p></li>
<li><p>si HIE donne une erreur dans VSCode, supprimer le dossier de cache <code>.cache/cabal-helper</code>.</p></li>
</ul>
<div><a href="https://linuxfr.org/users/nokomprendo-3/journaux/configurer-vscode-pour-haskell-debian-nix-nixos.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/118995/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/journaux/configurer-vscode-pour-haskell-debian-nix-nixos#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/118995/comments.atomtag:linuxfr.org,2005:Diary/388542019-12-24T12:37:28+01:002019-12-24T12:37:28+01:00Comprendre Go en 5 minutes, en HaskellLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>D'après leur réputation, Go et Haskell sont des langages de programmation assez opposés : Go privilégie la simplicité, Haskell l'expressivité. Cependant ces deux langages ne sont pas si éloignés que cela. Plus exactement, les concepts de base de Go se retrouvent, de façon assez ressemblante, en Haskell.</p>
<p>Ce journal reprend, en Haskell, les exemples de code en Go de l'article <a href="https://www.jesuisundev.com/comprendre-go-en-5-minutes/">Comprendre Go en 5 minutes</a>. L'objectif n'est pas de présenter exhaustivement Go ou Haskell, ni de démontrer que l'un est "meilleur" que l'autre mais plutôt de montrer l'équivalent en Haskell de quelques fonctionnalités de Go.</p>
<p>Avertissement : je ne connais rien à Go et ne suis pas un expert en Haskell. Les exemples ci-dessous présentent juste ce que j'en ai compris.</p>
<h2 id="toc-hello-world">Hello world</h2>
<p>Commençons par le traditionnel "helloworld". En Go, il faut définir un package, importer le module <code>fmt</code> et définir une fonction <code>main</code>.</p>
<pre><code class="go"><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"hello world"</span><span class="p">)</span>
<span class="p">}</span></code></pre>
<p>En Haskell, il faut également définir une fonction <code>main</code> mais il n'est pas nécessaire de définir un module ni d'importer de module particulier.</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="n">putStrLn</span> <span class="s">"hello world"</span></code></pre>
<h2 id="toc-fonctions">Fonctions</h2>
<p>En Go, la définition et l'évaluation de fonction ressemble à n'importe quel langage de type "langage C".</p>
<pre><code class="go"><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">func</span> <span class="nx">add</span><span class="p">(</span><span class="nx">x</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">y</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span> <span class="c1">// définit une fonction add</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">+</span> <span class="nx">y</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">j</span> <span class="kt">int</span> <span class="p">=</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">2</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">add</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">j</span><span class="p">))</span> <span class="c1">// évalue add</span>
<span class="p">}</span></code></pre>
<p>En Haskell, on utilise la forme curryfiée et sans parenthèse d'évaluation.</p>
<pre><code class="haskell"><span class="nf">add</span> <span class="ow">::</span> <span class="kt">Int</span> <span class="ow">-></span> <span class="kt">Int</span> <span class="ow">-></span> <span class="kt">Int</span> <span class="c1">-- définit une fonction add</span>
<span class="nf">add</span> <span class="n">x</span> <span class="n">y</span> <span class="ow">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">i</span> <span class="ow">=</span> <span class="mi">10</span>
<span class="n">j</span> <span class="ow">=</span> <span class="mi">2</span>
<span class="n">print</span> <span class="p">(</span><span class="n">add</span> <span class="n">i</span> <span class="n">j</span><span class="p">)</span> <span class="c1">-- évalue add</span></code></pre>
<h2 id="toc-interfaces">Interfaces</h2>
<p>Go permet de définir des types et des interfaces. Par exemple, on peut définir un type <code>GoDeveloper</code> implémentant une interface <code>Developer</code>.</p>
<pre><code class="go"><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">type</span> <span class="nx">Developer</span> <span class="kd">interface</span> <span class="p">{</span> <span class="c1">// définit une interface Developer</span>
<span class="nx">Code</span><span class="p">()</span> <span class="kt">string</span>
<span class="p">}</span>
<span class="kd">type</span> <span class="nx">GoDeveloper</span> <span class="kd">struct</span> <span class="p">{</span> <span class="c1">// définit un type GoDeveloper</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="p">(</span><span class="nx">g</span> <span class="nx">GoDeveloper</span> <span class="p">)</span> <span class="nx">Code</span><span class="p">()</span> <span class="kt">string</span> <span class="p">{</span> <span class="c1">// implémente Developer pour GoDeveloper</span>
<span class="k">return</span> <span class="s">"Go code"</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">goDeveloper</span> <span class="o">:=</span> <span class="nx">GoDeveloper</span><span class="p">{}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">goDeveloper</span><span class="p">.</span><span class="nx">Code</span><span class="p">())</span>
<span class="p">}</span></code></pre>
<p>Le système de type de Haskell est très évolué mais les classes de types et les types algébriques permettent d'écrire un code équivalent au code Go précédent.</p>
<pre><code class="haskell"><span class="kr">class</span> <span class="kt">Developer</span> <span class="n">a</span> <span class="kr">where</span> <span class="c1">-- définit une "interface" Developer</span>
<span class="n">code</span> <span class="ow">::</span> <span class="n">a</span> <span class="ow">-></span> <span class="kt">String</span>
<span class="kr">data</span> <span class="kt">GoDeveloper</span> <span class="ow">=</span> <span class="kt">GoDeveloper</span> <span class="c1">-- définit un type GoDeveloper</span>
<span class="kr">instance</span> <span class="kt">Developer</span> <span class="kt">GoDeveloper</span> <span class="kr">where</span> <span class="c1">-- implémente Developer pour GoDeveloper</span>
<span class="n">code</span> <span class="n">g</span> <span class="ow">=</span> <span class="s">"go code"</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">goDeveloper</span> <span class="ow">=</span> <span class="kt">GoDeveloper</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="n">code</span> <span class="n">goDeveloper</span><span class="p">)</span></code></pre>
<h2 id="toc-processus-légers">Processus légers</h2>
<p>Enfin, Go propose des processus légers, appelés "go routines" et lancés via le mot-clé <code>go</code>. </p>
<pre><code class="go"><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="s">"time"</span>
<span class="p">)</span>
<span class="kd">func</span> <span class="nx">say</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p"><</span> <span class="mi">5</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
<span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="mi">100</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">go</span> <span class="nx">say</span><span class="p">(</span><span class="s">"world"</span><span class="p">)</span> <span class="c1">// lance un processus léger, en parallèle</span>
<span class="nx">say</span><span class="p">(</span><span class="s">"hello"</span><span class="p">)</span>
<span class="p">}</span></code></pre>
<p>Haskell possède également des processus légers, lancés via <code>forkIO</code>.</p>
<pre><code class="haskell"><span class="kr">import</span> <span class="nn">Control.Concurrent</span>
<span class="kr">import</span> <span class="nn">Control.Monad</span>
<span class="kr">import</span> <span class="nn">System.Clock</span>
<span class="nf">say</span> <span class="ow">::</span> <span class="kt">String</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">say</span> <span class="n">s</span> <span class="ow">=</span> <span class="n">forM_</span> <span class="p">[</span><span class="mi">1</span> <span class="o">..</span> <span class="mi">5</span><span class="p">]</span> <span class="o">$</span> <span class="nf">\</span><span class="kr">_</span> <span class="ow">-></span> <span class="kr">do</span>
<span class="n">threadDelay</span> <span class="mi">100000</span>
<span class="n">putStrLn</span> <span class="n">s</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">forkIO</span> <span class="p">(</span><span class="n">say</span> <span class="s">"world"</span><span class="p">)</span> <span class="c1">-- lance un processus léger, en parallèle</span>
<span class="n">say</span> <span class="s">"hello"</span></code></pre>
<h2 id="toc-conclusion">Conclusion</h2>
<p>Le langage Go permet de définir et d'utiliser des fonctions, des types, des interfaces et des processus légers (appelés "go routines"). Ces fonctionnalités existent également en Haskell et s'utilisent de façon assez ressemblante.</p>
<div><a href="https://linuxfr.org/users/nokomprendo-3/journaux/comprendre-go-en-5-minutes-en-haskell.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/118985/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/journaux/comprendre-go-en-5-minutes-en-haskell#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/118985/comments.atomtag:linuxfr.org,2005:Diary/382772018-12-14T10:54:16+01:002018-12-14T10:54:16+01:00Un serveur de webcam en 35 lignes de HaskellLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-capture-vid%C3%A9o">Capture vidéo</a></li>
<li><a href="#toc-serveur-web">Serveur web</a></li>
<li><a href="#toc-g%C3%A9rer-plusieurs-clients">Gérer plusieurs clients</a></li>
<li><a href="#toc-r%C3%A9capitulatif">Récapitulatif</a></li>
</ul>
<p>Pour mettre en place une webcam, on connecte une caméra à un ordinateur sur lequel on fait tourner un serveur retransmettant les images. Celles-ci sont alors accessibles via des requêtes au serveur.</p>
<p>Cet article présente comment implémenter un serveur de webcam en Haskell. Le serveur proposé transmet l'image courante en réponse aux requêtes HTTP. En parallèle (via un thread léger), il met également à jour l'image courante à partir du flux vidéo.</p>
<p><a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_25/webcamer">code source du projet</a></p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32352f696d616765732f77656263616d65722e676966/webcamer.gif" alt="" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_25/images/webcamer.gif"></p>
<h2 id="toc-capture-vidéo">Capture vidéo</h2>
<p>Tout d'abord, on a besoin d'ouvrir et de capturer le flux vidéo de la webcam. Ceci est très facile à faire avec <a href="https://opencv.org/">OpenCV</a>, une bibliothèque classique de traitement d'images et de vision artificielle. OpenCV est implémentée en C++ mais possède des interfaces pour de nombreux autres langages, notamment pour Haskell avec <a href="https://github.com/LumiGuide/haskell-opencv">haskell-opencv</a>.</p>
<p>Dans le code suivant, la fonction <code>openCam</code> ouvre le premier périphérique vidéo (id 0) et configure sa fréquence de rafraichissement à 5 images par seconde. Puis la fonction <code>captureCam</code> lit une image OpenCV (de type <code>Mat ('S ['D, 'D]) 'D 'D</code>) depuis le périphérique vidéo (de type <code>VideoCapture</code>). Enfin, la fonction <code>imgToPng</code> convertit une image OpenCV en image PNG affichage par un navigateur web.</p>
<pre><code class="haskell"><span class="cm">{-# language DataKinds #-}</span>
<span class="cm">{-# LANGUAGE OverloadedStrings #-}</span>
<span class="kr">import</span> <span class="nn">Control.Concurrent</span> <span class="p">(</span><span class="nf">forkIO</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad</span> <span class="p">(</span><span class="nf">forever</span><span class="p">,</span> <span class="nf">unless</span><span class="p">,</span> <span class="nf">liftM</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.ByteString</span> <span class="p">(</span><span class="kt">ByteString</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.ByteString.Lazy</span> <span class="p">(</span><span class="nf">fromStrict</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.IORef</span> <span class="p">(</span><span class="nf">atomicWriteIORef</span><span class="p">,</span> <span class="kt">IORef</span><span class="p">,</span> <span class="nf">newIORef</span><span class="p">,</span> <span class="nf">readIORef</span><span class="p">)</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Web.Scotty</span> <span class="k">as</span> <span class="n">SC</span>
<span class="kr">import</span> <span class="nn">OpenCV</span>
<span class="kr">import</span> <span class="nn">OpenCV.VideoIO.Types</span>
<span class="nf">openCam</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="kt">VideoCapture</span><span class="p">)</span>
<span class="nf">openCam</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">cap</span> <span class="ow"><-</span> <span class="n">newVideoCapture</span>
<span class="n">exceptErrorIO</span> <span class="o">$</span> <span class="n">videoCaptureOpen</span> <span class="n">cap</span> <span class="o">$</span> <span class="kt">VideoDeviceSource</span> <span class="mi">0</span> <span class="kt">Nothing</span>
<span class="n">isOpened</span> <span class="ow"><-</span> <span class="n">videoCaptureIsOpened</span> <span class="n">cap</span>
<span class="kr">case</span> <span class="n">isOpened</span> <span class="kr">of</span>
<span class="kt">False</span> <span class="ow">-></span> <span class="n">return</span> <span class="kt">Nothing</span>
<span class="kt">True</span> <span class="ow">-></span> <span class="n">videoCaptureSetD</span> <span class="n">cap</span> <span class="kt">VideoCapPropFps</span> <span class="mi">5</span> <span class="o">>></span> <span class="p">(</span><span class="n">return</span> <span class="o">$</span> <span class="kt">Just</span> <span class="n">cap</span><span class="p">)</span>
<span class="nf">captureCam</span> <span class="ow">::</span> <span class="kt">VideoCapture</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="p">(</span><span class="kt">Mat</span> <span class="p">(</span><span class="kt">'S</span> <span class="p">[</span><span class="kt">'D</span><span class="p">,</span> <span class="kt">'D</span><span class="p">])</span> <span class="kt">'D</span> <span class="kt">'D</span><span class="p">))</span>
<span class="nf">captureCam</span> <span class="n">cap</span> <span class="ow">=</span> <span class="n">videoCaptureGrab</span> <span class="n">cap</span> <span class="o">>></span> <span class="n">videoCaptureRetrieve</span> <span class="n">cap</span>
<span class="nf">imgToPng</span> <span class="ow">::</span> <span class="kt">Mat</span> <span class="p">(</span><span class="kt">'S</span> <span class="p">[</span><span class="kt">'D</span><span class="p">,</span> <span class="kt">'D</span><span class="p">])</span> <span class="kt">'D</span> <span class="kt">'D</span> <span class="ow">-></span> <span class="kt">ByteString</span>
<span class="nf">imgToPng</span> <span class="ow">=</span> <span class="n">exceptError</span> <span class="o">.</span> <span class="n">imencode</span> <span class="p">(</span><span class="kt">OutputPng</span> <span class="n">defaultPngParams</span><span class="p">)</span></code></pre>
<p>On peut tester ces fonctions localement, avec le code suivant. La fonction <code>loopCam</code> lit une image (en utilisant <code>captureCam</code>), affiche cette image dans une fenêtre et boucle récursivement tant qu'on n'a pas appuyé sur la touche Echap. La fonction principale <code>main</code> se résume à ouvrir un périphérique vidéo (avec <code>openCam</code>), à créer une fenêtre et à lancer la boucle <code>loopCam</code>.</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">capMaybe</span> <span class="ow"><-</span> <span class="n">openCam</span>
<span class="kr">case</span> <span class="n">capMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">putStrLn</span> <span class="s">"couldn't open device"</span>
<span class="kt">Just</span> <span class="n">cap</span> <span class="ow">-></span> <span class="n">withWindow</span> <span class="s">"webcamer"</span> <span class="p">(</span><span class="n">loopCam</span> <span class="n">cap</span><span class="p">)</span>
<span class="nf">loopCam</span> <span class="ow">::</span> <span class="kt">VideoCapture</span> <span class="ow">-></span> <span class="kt">Window</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">loopCam</span> <span class="n">cap</span> <span class="n">window</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">imgMaybe</span> <span class="ow"><-</span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="kr">case</span> <span class="n">imgMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">return</span> <span class="nb">()</span>
<span class="kt">Just</span> <span class="n">img</span> <span class="ow">-></span> <span class="kr">do</span>
<span class="n">imshow</span> <span class="n">window</span> <span class="n">img</span>
<span class="n">key</span> <span class="ow"><-</span> <span class="n">waitKey</span> <span class="mi">20</span>
<span class="n">unless</span> <span class="p">(</span><span class="n">key</span> <span class="o">==</span> <span class="mi">27</span><span class="p">)</span> <span class="o">$</span> <span class="n">loopCam</span> <span class="n">cap</span> <span class="n">window</span></code></pre>
<p>Si on exécute ce code, on devrait avoir une fenêtre affichant le flux vidéo de la webcam à 5 FPS.</p>
<h2 id="toc-serveur-web">Serveur web</h2>
<p>En utilisant la bibliothèque <a href="https://hackage.haskell.org/package/scotty">scotty</a>, créons maintenant un serveur web qui va fournir le flux vidéo. À la place des fonctions <code>main</code> et <code>loopCam</code> précédentes, la fonction <code>main</code> suivante ouvre le périphérique vidéo et lance <code>runServer</code>. Cette fonction <code>runServer</code> lance un serveur scotty qui fournit deux routes. Pour la route principale "/", le serveur fournit la page principale (le fichier <code>index.html</code>). Pour la route "/out.png", il lit une image depuis la webcam, la convertit en PNG puis l'envoie au client HTTP.</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">capMaybe</span> <span class="ow"><-</span> <span class="n">openCam</span>
<span class="kr">case</span> <span class="n">capMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">putStrLn</span> <span class="s">"couldn't open device"</span>
<span class="kt">Just</span> <span class="n">cap</span> <span class="ow">-></span> <span class="n">runServer</span> <span class="mi">3042</span> <span class="n">cap</span>
<span class="nf">runServer</span> <span class="ow">::</span> <span class="kt">Int</span> <span class="ow">-></span> <span class="kt">VideoCapture</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">runServer</span> <span class="n">port</span> <span class="n">cap</span> <span class="ow">=</span> <span class="kt">SC</span><span class="o">.</span><span class="n">scotty</span> <span class="n">port</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">get</span> <span class="s">"/"</span> <span class="o">$</span> <span class="kt">SC</span><span class="o">.</span><span class="n">file</span> <span class="s">"index.html"</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">get</span> <span class="s">"/out.png"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">setHeader</span> <span class="s">"Content-Type"</span> <span class="s">"image/png"</span>
<span class="n">imgMaybe</span> <span class="ow"><-</span> <span class="kt">SC</span><span class="o">.</span><span class="n">liftAndCatchIO</span> <span class="o">$</span> <span class="n">liftM</span> <span class="n">imgToPng</span> <span class="o"><$></span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="kr">case</span> <span class="n">imgMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">return</span> <span class="nb">()</span>
<span class="kt">Just</span> <span class="n">img</span> <span class="ow">-></span> <span class="kt">SC</span><span class="o">.</span><span class="n">raw</span> <span class="o">$</span> <span class="n">fromStrict</span> <span class="n">img</span></code></pre>
<p>Ce serveur web envoie une image à la demande du client. Pour afficher le flux vidéo, le client doit donc régulièrement demander une nouvelle image. Ceci est fait dans la page <code>index.html</code> : la fonction <code>updateImg</code> demande la route "/out.png" au server puis met à jour la page HTML quand l'image a été reçue. Cette fonction est appelée toutes les 200 ms (c'est-à-dire à 5 FPS), grâce à la fonction JavaScript <code>setInterval</code>.</p>
<pre><code class="html"><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">/></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>webcamer<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">img</span> <span class="na">id</span><span class="o">=</span><span class="s">"my_img"</span><span class="p">></span> <span class="p"></</span><span class="nt">img</span><span class="p">></span>
<span class="p"><</span><span class="nt">script</span><span class="p">></span>
<span class="kd">function</span> <span class="nx">updateImg</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">"out.png"</span><span class="p">)</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="p">=></span> <span class="nx">response</span><span class="p">.</span><span class="nx">blob</span><span class="p">())</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">myBlob</span><span class="p">){</span>
<span class="nx">URL</span><span class="p">.</span><span class="nx">revokeObjectURL</span><span class="p">(</span><span class="nx">my_img</span><span class="p">.</span><span class="nx">src</span><span class="p">);</span>
<span class="nx">my_img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="nx">myBlob</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kr">const</span> <span class="nx">my_interval</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(</span><span class="nx">updateImg</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
<span class="p"></</span><span class="nt">script</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span></code></pre>
<h2 id="toc-gérer-plusieurs-clients">Gérer plusieurs clients</h2>
<p>Le serveur web précédent lit une image, depuis le flux vidéo, quand un client demande la route "/out.png". Cependant, ceci ne fonctionne plus s'il y a plusieurs clients car le flux vidéo ne fournit plus assez d'images. Pour résoudre ce problème, il suffit de lire le flux et de gérer les requêtes HTTP de façon indépendante.</p>
<p>Le code suivant utilise une référence mutable <a href="https://hackage.haskell.org/package/base/docs/Data-IORef.html">IORef</a> pour stocker l'image courante. Cette image est lue dans la fonction <code>runServer</code> quand un client envoie une requête HTTP, et elle est modifiée dans la fonction <code>runCam</code> quand une nouvelle image est disponible depuis le flux vidéo. Finallement, la fonction <code>main</code> se résume à initialiser la référence mutable et à lancer <code>runServer</code> et <code>runCam</code> en parallèle, via <code>forkIO</code> (threads légers).</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">capMaybe</span> <span class="ow"><-</span> <span class="n">openCam</span>
<span class="kr">case</span> <span class="n">capMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">putStrLn</span> <span class="s">"couldn't open device"</span>
<span class="kt">Just</span> <span class="n">cap</span> <span class="ow">-></span> <span class="kr">do</span>
<span class="kt">Just</span> <span class="n">png0</span> <span class="ow"><-</span> <span class="n">liftM</span> <span class="n">imgToPng</span> <span class="o"><$></span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="n">pngRef</span> <span class="ow"><-</span> <span class="n">newIORef</span> <span class="n">png0</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">forkIO</span> <span class="o">$</span> <span class="n">runCam</span> <span class="n">cap</span> <span class="n">pngRef</span>
<span class="n">runServer</span> <span class="mi">3042</span> <span class="n">pngRef</span>
<span class="nf">runServer</span> <span class="ow">::</span> <span class="kt">Int</span> <span class="ow">-></span> <span class="kt">IORef</span> <span class="kt">ByteString</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">runServer</span> <span class="n">port</span> <span class="n">pngRef</span> <span class="ow">=</span> <span class="kt">SC</span><span class="o">.</span><span class="n">scotty</span> <span class="n">port</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">get</span> <span class="s">"/"</span> <span class="o">$</span> <span class="kt">SC</span><span class="o">.</span><span class="n">file</span> <span class="s">"index.html"</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">get</span> <span class="s">"/out.png"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">setHeader</span> <span class="s">"Content-Type"</span> <span class="s">"image/png"</span>
<span class="n">img</span> <span class="ow"><-</span> <span class="kt">SC</span><span class="o">.</span><span class="n">liftAndCatchIO</span> <span class="p">(</span><span class="n">readIORef</span> <span class="n">pngRef</span><span class="p">)</span>
<span class="kt">SC</span><span class="o">.</span><span class="n">raw</span> <span class="o">$</span> <span class="n">fromStrict</span> <span class="n">img</span>
<span class="nf">runCam</span> <span class="ow">::</span> <span class="kt">VideoCapture</span> <span class="ow">-></span> <span class="kt">IORef</span> <span class="kt">ByteString</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">runCam</span> <span class="n">cap</span> <span class="n">pngRef</span> <span class="ow">=</span> <span class="n">forever</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">imgMaybe</span> <span class="ow"><-</span> <span class="n">liftM</span> <span class="n">imgToPng</span> <span class="o"><$></span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="n">maybe</span> <span class="p">(</span><span class="n">return</span> <span class="nb">()</span><span class="p">)</span> <span class="p">(</span><span class="n">atomicWriteIORef</span> <span class="n">pngRef</span><span class="p">)</span> <span class="n">imgMaybe</span></code></pre>
<p>Ainsi, si plusieurs clients HTTP demandent une image alors qu'une seule image est disponible dans le flux vidéo durant ce laps de temps, le serveur envoie la même image et le flux s'affiche correctement chez tous les clients.</p>
<h2 id="toc-récapitulatif">Récapitulatif</h2>
<p>Le code final est résumé ci-dessous. Il gère la capture vidéo de la webcam, le service web et les clients multiples. Le tout en 35 lignes de Haskell (sans les commentaires ni les signatures de fonctions, mais fonctionnel quand même).</p>
<pre><code class="haskell"><span class="cm">{-# LANGUAGE OverloadedStrings #-}</span>
<span class="kr">import</span> <span class="nn">Control.Concurrent</span> <span class="p">(</span><span class="nf">forkIO</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad</span> <span class="p">(</span><span class="nf">forever</span><span class="p">,</span> <span class="nf">liftM</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.ByteString.Lazy</span> <span class="p">(</span><span class="nf">fromStrict</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.IORef</span> <span class="p">(</span><span class="nf">atomicWriteIORef</span><span class="p">,</span> <span class="nf">newIORef</span><span class="p">,</span> <span class="nf">readIORef</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Web.Scotty</span> <span class="p">(</span><span class="nf">get</span><span class="p">,</span> <span class="nf">file</span><span class="p">,</span> <span class="nf">raw</span><span class="p">,</span> <span class="nf">scotty</span><span class="p">,</span> <span class="nf">liftAndCatchIO</span><span class="p">,</span> <span class="nf">setHeader</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">OpenCV</span>
<span class="kr">import</span> <span class="nn">OpenCV.VideoIO.Types</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">capMaybe</span> <span class="ow"><-</span> <span class="n">openCam</span>
<span class="kr">case</span> <span class="n">capMaybe</span> <span class="kr">of</span>
<span class="kt">Nothing</span> <span class="ow">-></span> <span class="n">putStrLn</span> <span class="s">"couldn't open device"</span>
<span class="kt">Just</span> <span class="n">cap</span> <span class="ow">-></span> <span class="kr">do</span>
<span class="kt">Just</span> <span class="n">png0</span> <span class="ow"><-</span> <span class="n">liftM</span> <span class="n">imgToPng</span> <span class="o"><$></span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="n">pngRef</span> <span class="ow"><-</span> <span class="n">newIORef</span> <span class="n">png0</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">forkIO</span> <span class="o">$</span> <span class="n">runCam</span> <span class="n">cap</span> <span class="n">pngRef</span>
<span class="n">runServer</span> <span class="mi">3042</span> <span class="n">pngRef</span>
<span class="nf">runServer</span> <span class="n">port</span> <span class="n">pngRef</span> <span class="ow">=</span> <span class="n">scotty</span> <span class="n">port</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">get</span> <span class="s">"/"</span> <span class="o">$</span> <span class="n">file</span> <span class="s">"index.html"</span>
<span class="n">get</span> <span class="s">"/out.png"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">setHeader</span> <span class="s">"Content-Type"</span> <span class="s">"image/png"</span>
<span class="n">img</span> <span class="ow"><-</span> <span class="n">liftAndCatchIO</span> <span class="p">(</span><span class="n">readIORef</span> <span class="n">pngRef</span><span class="p">)</span>
<span class="n">raw</span> <span class="o">$</span> <span class="n">fromStrict</span> <span class="n">img</span>
<span class="nf">runCam</span> <span class="n">cap</span> <span class="n">pngRef</span> <span class="ow">=</span> <span class="n">forever</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">imgMaybe</span> <span class="ow"><-</span> <span class="n">liftM</span> <span class="n">imgToPng</span> <span class="o"><$></span> <span class="n">captureCam</span> <span class="n">cap</span>
<span class="n">maybe</span> <span class="p">(</span><span class="n">return</span> <span class="nb">()</span><span class="p">)</span> <span class="p">(</span><span class="n">atomicWriteIORef</span> <span class="n">pngRef</span><span class="p">)</span> <span class="n">imgMaybe</span>
<span class="nf">openCam</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">cap</span> <span class="ow"><-</span> <span class="n">newVideoCapture</span>
<span class="n">exceptErrorIO</span> <span class="o">$</span> <span class="n">videoCaptureOpen</span> <span class="n">cap</span> <span class="o">$</span> <span class="kt">VideoDeviceSource</span> <span class="mi">0</span> <span class="kt">Nothing</span>
<span class="n">isOpened</span> <span class="ow"><-</span> <span class="n">videoCaptureIsOpened</span> <span class="n">cap</span>
<span class="kr">case</span> <span class="n">isOpened</span> <span class="kr">of</span>
<span class="kt">False</span> <span class="ow">-></span> <span class="n">return</span> <span class="kt">Nothing</span>
<span class="kt">True</span> <span class="ow">-></span> <span class="n">videoCaptureSetD</span> <span class="n">cap</span> <span class="kt">VideoCapPropFps</span> <span class="mi">5</span> <span class="o">>></span> <span class="p">(</span><span class="n">return</span> <span class="o">$</span> <span class="kt">Just</span> <span class="n">cap</span><span class="p">)</span>
<span class="nf">captureCam</span> <span class="n">cap</span> <span class="ow">=</span> <span class="n">videoCaptureGrab</span> <span class="n">cap</span> <span class="o">>></span> <span class="n">videoCaptureRetrieve</span> <span class="n">cap</span>
<span class="nf">imgToPng</span> <span class="ow">=</span> <span class="n">exceptError</span> <span class="o">.</span> <span class="n">imencode</span> <span class="p">(</span><span class="kt">OutputPng</span> <span class="n">defaultPngParams</span><span class="p">)</span></code></pre>
<div><a href="https://linuxfr.org/users/nokomprendo-3/journaux/un-serveur-de-webcam-en-35-lignes-de-haskell.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/115984/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/journaux/un-serveur-de-webcam-en-35-lignes-de-haskell#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/115984/comments.atomtag:linuxfr.org,2005:News/386862018-09-25T19:46:49+02:002018-10-14T19:07:42+02:00GHC 8.4 et 8.6Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>GHC, le compilateur Haskell est sorti en version 8.6.1 le 22 septembre 2018.<br>
Cette dépêche détaille les nouveautés. De plus, nous n’avions pas fait de dépêche pour la version 8.4.1 du 8 mars 2018, ainsi que pour les versions 8.4.2 et 8.4.3 ayant suivi en avril et mai ; cette dépêche tente de combler ce vide.</p>
<p>Les versions <a href="https://mail.haskell.org/pipermail/haskell-cafe/2018-April/128950.html">8.4.2</a> et <a href="https://mail.haskell.org/pipermail/haskell-cafe/2018-May/129193.html">8.4.3</a> étant principalement des versions mineures consacrées aux corrections de bogues critiques, celles‐ci ne seront pas traitées dans cette dépêche. </p>
<p>Comme d’habitude pour les dépêches concernant les sorties de GHC, nous reviendrons sur les nouveautés de ces versions pour conclure par un petit exemple de Haskell pour vous donner envie d’utiliser ce langage.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/news/sortie-de-ghc-8-2-1" hreflang="fr" href="https://linuxfr.org/redirect/102323">Précédente dépêche pour la sortie de GHC 8.2</a></li><li>lien nᵒ 2 : <a title="http://lyah.haskell.fr" hreflang="en" href="https://linuxfr.org/redirect/102569">Apprendre Haskell vous fera le plus grand bien !</a></li><li>lien nᵒ 3 : <a title="https://www.haskell.org/onlinereport/haskell2010" hreflang="en" href="https://linuxfr.org/redirect/102751">Le document normalisant/standardisant Haskell : Haskell Report, Version 2010</a></li><li>lien nᵒ 4 : <a title="https://mail.haskell.org/pipermail/haskell-cafe/2018-March/128730.html" hreflang="en" href="https://linuxfr.org/redirect/102752">Annonce de la version 8.4.1</a></li><li>lien nᵒ 5 : <a title="https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/8.4.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/102753">Notes et guide utilisateur pour cette v8.4.1</a></li><li>lien nᵒ 6 : <a title="https://ghc.haskell.org/trac/ghc/blog/ghc-8.6.1-released" hreflang="en" href="https://linuxfr.org/redirect/102754">Annonce de la version 8.6.1</a></li><li>lien nᵒ 7 : <a title="https://downloads.haskell.org/~ghc/8.6.1/docs/html/users_guide/8.6.1-notes.html" hreflang="en" href="https://linuxfr.org/redirect/102755">Notes et guide utilisateur pour cette v8.6.1</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-haskell">Haskell</a></li>
<li><a href="#toc-version-84">Version 8.4</a></li>
<li>
<a href="#toc-version-86">Version 8.6</a><ul>
<li>
<a href="#toc-extension-%C3%A0-la-syntaxe">Extension à la syntaxe</a><ul>
<li><a href="#toc-numericunderscores"><code>NumericUnderscores</code></a></li>
<li><a href="#toc-blockarguments"><code>BlockArguments</code></a></li>
</ul>
</li>
<li>
<a href="#toc-environment-de-development">Environment de development</a><ul>
<li><a href="#toc-am%C3%A9liorations-des-typedholes">Améliorations des « typed holes »</a></li>
<li><a href="#toc-command-doc-dans-le-shell">Command <code>:doc</code> dans le shell</a></li>
</ul>
</li>
<li>
<a href="#toc-extension-au-m%C3%A9canisme-de-programmation-g%C3%A9n%C3%A9rique">Extension au mécanisme de programmation générique</a><ul>
<li>
<a href="#toc-derivingvia"><code>DerivingVia</code></a><ul>
<li><a href="#toc-notion-dinstance-et-de-class">Notion d'<code>instance</code> et de <code>class</code></a></li>
<li><a href="#toc-deriving"><code>deriving</code></a></li>
<li><a href="#toc-derivingvia-1"><code>DerivingVia</code></a></li>
</ul>
</li>
</ul>
</li>
<li>
<a href="#toc-greffons-de-compilation">Greffons de compilation</a><ul>
<li><a href="#toc-les-greffons-sources">Les greffons sources</a></li>
</ul>
</li>
<li><a href="#toc-quantified-constraints">Quantified Constraints</a></li>
<li><a href="#toc-plus-de-tests-dexhaustivit%C3%A9">Plus de tests d’exhaustivité</a></li>
<li><a href="#toc-plus-de-constant-folding">Plus de constant folding</a></li>
<li><a href="#toc-extension-de-partialtypesignatures">Extension de <code>PartialTypeSignatures</code></a></li>
<li><a href="#toc-heap-view">Heap View</a></li>
<li><a href="#toc-monadfail">MonadFail</a></li>
<li><a href="#toc-starisstar"><code>StarIsStar</code></a></li>
</ul>
</li>
<li><a href="#toc-analyser-le-journal-des-modifications-de-linuxfrorg">Analyser le journal des modifications de LinuxFr.org</a></li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<h2 id="toc-haskell">Haskell</h2>
<p>Haskell est un langage de programmation fonctionnel pur et paresseux.</p>
<p>Le « fonctionnel » signifie qu’on axe l’écriture d’un programme plus sur la transformation des données, que sur des séquences d’instructions à exécuter (contrairement à un langage impératif).</p>
<p>Le « pur » signifie qu’on limite au maximum les effets de bord, par exemple, il n’est pas possible de modifier une valeur.</p>
<p>Le « paresseux » signifie que les calculs décrits ne seront réalisés qu’au moment où l’on en aura besoin. Cet aspect permet d’avoir des listes infinies en données, par exemple.</p>
<h2 id="toc-version-84">Version 8.4</h2>
<p>Cette version a apporté beaucoup de nouveautés pour les utilisateurs et utilisatrices chevronnés de GHC. Listons rapidement quelques points :</p>
<ul>
<li>L'extension <code>TypeInType</code> a subi des retouches, notamment en cessant de traiter l'opérateur <code>*</code> de façon différente du reste des opérateurs. Rappelons qu'avec cette extension et au niveau des types, cet opérateur n'indique plus le type <code>Kind</code> d'un type : un type est un <code>Type</code>, d'où le nom de l'extension. Cette notion n'est pas très rigoureuse mais GHC n'est pas non plus un outil de preuve mécanique ! En réalité, <code>TypeInType</code> est un ajout incrémental au fonctionnalités de l'extension <code>PolyKinds</code> et apparentées et sera complètement absorbée & dépréciée par celles-ci dès la version 8.6.</li>
<li>Le découplage des classes <code>Semigroup</code> et <code>Monoid</code> a franchi une nouvelle étape. Pour rappel, le changement le plus visible est que l'opérateur <code><></code> se trouve désormais dans le module <code>Data.Semigroup</code> alors qu'avant le découplage il appartenait à <code>Data.Monoid</code>. Conceptuellement, les <code>Semigroup</code> sont l'ensemble des types qui admettent une opération binaire associative, comme le <code>+</code>. Un <code>Monoid</code> est un <code>Semigroup</code> a qui on ajoute un élément neutre, comme le <code>0</code>.</li>
<li>Les constantes litérales hexadécimales pour les nombres flottants, grâce à l'extension <code>HexFloatLiterals</code>, permettent de representer un nombre flottant en hexadécimal, <code>0x0.1p4 == 1.0</code>.</li>
</ul>
<h2 id="toc-version-86">Version 8.6</h2>
<p>La suite de cette dépêche est consacrée aux changements de GHC 8.6. Cette liste n'est pas exhaustive et représente la vision des auteurs sur les points importants, amusants, ou pouvant être illustrés simplement.</p>
<p>Les nouveautés sont intégrées au sein de GHC par le biais d'extensions, que l'utilisateur aura la liberté d'activer au non. Certaines extensions deviennent officielles lors de la rédaction du <a href="https://www.haskell.org/onlinereport/haskell2010/">Haskell Report</a>, le dernier datant de 2010 et le prochain étant prévu pour 2020.</p>
<p>Entre temps, les développeurs Haskell doivent activer de nombreuses extensions. Sous une apparente contrainte, cela apporte au langage une capacité d'évolution assez intéressante puisque les développeurs ne se refusent pas à implémenter des changements importants sous la forme d'extensions.</p>
<h3 id="toc-extension-à-la-syntaxe">Extension à la syntaxe</h3>
<h4 id="toc-numericunderscores"><code>NumericUnderscores</code></h4>
<p>L'extension <code>NumericUnderscores</code> permet de mettre des <code>_</code> dans ses nombres, comme <code>123_456_789</code>. Cela aide la lisibilité.</p>
<p>Notons que jusqu'à présent on pouvait facilement exprimer des grands nombres "ronds" en utilisant la notation exponentielle, l'extension <code>NumDecimals</code> permettant d'obtenir des nombres entiers le cas échéant.</p>
<pre><code class="haskell"><span class="kt">Prelude</span><span class="o">></span> <span class="mf">1e3</span>
<span class="mi">1000</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mf">1.123e3</span>
<span class="mi">1123</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="kr">type</span> <span class="mf">1.123e3</span>
<span class="mf">1.123e3</span> <span class="ow">::</span> <span class="kt">Num</span> <span class="n">p</span> <span class="ow">=></span> <span class="n">p</span> <span class="c1">-- Un nombre, autant un flottant qu'un entier</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="n">t</span> <span class="mf">1.1234e3</span>
<span class="mf">1.1234e3</span> <span class="ow">::</span> <span class="kt">Fractional</span> <span class="n">p</span> <span class="ow">=></span> <span class="n">p</span> <span class="c1">-- Ici c'est forcement un flottant, car la valeur 1123.4 ne peut pas être représentée en entier.</span>
<span class="c1">-- La nouvelle syntaxe</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">1</span><span class="n">_123</span>
<span class="mi">1123</span></code></pre>
<h4 id="toc-blockarguments"><code>BlockArguments</code></h4>
<p>La nouvelle extension <code>BlockArguments</code> permet un groupement implicite différent des expressions et tout particulièrement des blocs <code>do</code> et des fonctions anonymes.</p>
<p>Par exemple, le bloc suivant va demander le nom de l'utilisateur et le saluer :</p>
<pre><code class="haskell"><span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"Quel est votre nom ?"</span>
<span class="n">name</span> <span class="ow"><-</span> <span class="n">getLine</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">name</span><span class="p">)</span></code></pre>
<p>Pour effectuer cette action en boucle, on peut utiliser <code>forever</code> qui prend en argument l'action à répéter à l'infini. Jusqu'alors, il fallait mettre des parenthèses disgracieuses à mon goût :</p>
<pre><code class="haskell"><span class="nf">forever</span> <span class="p">(</span><span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"Quel est votre nom ?"</span>
<span class="n">name</span> <span class="ow"><-</span> <span class="n">getLine</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">name</span><span class="p">)</span>
<span class="p">)</span></code></pre>
<p>Ou utiliser l'opérateur <code>($)</code> qui est peu apprécié car déroutant pour les débutants :</p>
<pre><code class="haskell"><span class="nf">forever</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"Quel est votre nom ?"</span>
<span class="n">name</span> <span class="ow"><-</span> <span class="n">getLine</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">name</span><span class="p">)</span></code></pre>
<p>Dans les deux cas, c'est fort peu confortable.</p>
<p>La nouvelle syntaxe, avec l'extension <code>BlockArguments</code>, est maintenant :</p>
<pre><code class="haskell"><span class="nf">forever</span> <span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"Quel est votre nom ?"</span>
<span class="n">name</span> <span class="ow"><-</span> <span class="n">getLine</span>
<span class="n">putStrLn</span> <span class="p">(</span><span class="s">"Bonjour "</span> <span class="o">++</span> <span class="n">name</span><span class="p">)</span></code></pre>
<p>Cela prend tout son sens sur des tests unitaires, comme avec <code>HSpec</code> :</p>
<pre><code class="haskell"><span class="nf">describe</span> <span class="s">"factorials"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">it</span> <span class="s">"must be equal 1 when argument is 0"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">fact</span> <span class="mi">0</span> <span class="p">`</span><span class="n">shouldBe</span><span class="p">`</span> <span class="mi">1</span>
<span class="n">it</span> <span class="s">"must always be positive "</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">property</span> <span class="o">$</span> <span class="nf">\</span><span class="p">(</span><span class="kt">Positive</span> <span class="n">x</span><span class="p">)</span> <span class="ow">-></span> <span class="n">fact</span> <span class="n">x</span> <span class="o">>=</span> <span class="mi">1</span>
<span class="n">it</span> <span class="s">"is strictly growing"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">property</span> <span class="o">$</span> <span class="nf">\</span><span class="p">(</span><span class="kt">Positive</span> <span class="n">x</span><span class="p">)</span> <span class="ow">-></span> <span class="n">fact</span> <span class="n">x</span> <span class="o"><</span> <span class="n">fact</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span></code></pre>
<p>Notez l'abus de <code>($)</code>, qui peut maintenant se remplacer par : </p>
<pre><code class="haskell"><span class="nf">describe</span> <span class="s">"factorials"</span> <span class="kr">do</span>
<span class="n">it</span> <span class="s">"must be equal 1 when argument is 0"</span> <span class="kr">do</span>
<span class="n">fact</span> <span class="mi">0</span> <span class="p">`</span><span class="n">shouldBe</span><span class="p">`</span> <span class="mi">1</span>
<span class="n">it</span> <span class="s">"must always be positive"</span> <span class="kr">do</span>
<span class="n">property</span> <span class="nf">\</span><span class="p">(</span><span class="kt">Positive</span> <span class="n">x</span><span class="p">)</span> <span class="ow">-></span> <span class="n">fact</span> <span class="n">x</span> <span class="o">>=</span> <span class="mi">1</span>
<span class="n">it</span> <span class="s">"is strictly growing"</span> <span class="kr">do</span>
<span class="n">property</span> <span class="nf">\</span><span class="p">(</span><span class="kt">Positive</span> <span class="n">x</span><span class="p">)</span> <span class="ow">-></span> <span class="n">fact</span> <span class="n">x</span> <span class="o"><</span> <span class="n">fact</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span></code></pre>
<h3 id="toc-environment-de-development">Environment de development</h3>
<h4 id="toc-améliorations-des-typedholes">Améliorations des « typed holes »</h4>
<p>Les typed holes sont une syntaxe où le développeur peut laisser des trous dans son programme et demander au compilateur de l'aide pour les combler.</p>
<pre><code class="haskell"><span class="kr">_</span> <span class="o">&&</span> <span class="kt">True</span></code></pre>
<p>Cette expression évalue le ET booléan entre <code>_</code> et <code>True</code>. <code>_</code> est un <code>typed hole</code>, ainsi le compilateur va simplement nous renvoyer un message d'erreur incluant le type déduit de l'expression, ici <code>Bool</code> :</p>
<pre><code><interactive>:28:1: error:
• Found hole: _ :: Bool
• In the first argument of ‘(&&)’, namely ‘_’
In the expression: _ && True
...
</code></pre>
<p>Depuis GHC 8.6, le support s'améliore et le compilateur fait aussi des suggestions à partir des symboles actuellements accessibles :</p>
<pre><code class="haskell"> <span class="kt">Valid</span> <span class="n">hole</span> <span class="n">fits</span> <span class="n">include</span>
<span class="n">otherwise</span> <span class="ow">::</span> <span class="kt">Bool</span>
<span class="p">(</span><span class="n">imported</span> <span class="n">from</span> <span class="err">‘</span><span class="kt">Prelude</span><span class="err">’</span> <span class="p">(</span><span class="n">and</span> <span class="n">originally</span> <span class="n">defined</span> <span class="kr">in</span> <span class="err">‘</span><span class="kt">GHC</span><span class="o">.</span><span class="kt">Base</span><span class="err">’</span><span class="p">))</span>
<span class="kt">False</span> <span class="ow">::</span> <span class="kt">Bool</span>
<span class="p">(</span><span class="n">imported</span> <span class="n">from</span> <span class="err">‘</span><span class="kt">Prelude</span><span class="err">’</span> <span class="p">(</span><span class="n">and</span> <span class="n">originally</span> <span class="n">defined</span> <span class="kr">in</span> <span class="err">‘</span><span class="kt">GHC</span><span class="o">.</span><span class="kt">Types</span><span class="err">’</span><span class="p">))</span>
<span class="kt">True</span> <span class="ow">::</span> <span class="kt">Bool</span>
<span class="p">(</span><span class="n">imported</span> <span class="n">from</span> <span class="err">‘</span><span class="kt">Prelude</span><span class="err">’</span> <span class="p">(</span><span class="n">and</span> <span class="n">originally</span> <span class="n">defined</span> <span class="kr">in</span> <span class="err">‘</span><span class="kt">GHC</span><span class="o">.</span><span class="kt">Types</span><span class="err">’</span><span class="p">))</span></code></pre>
<p>À terme on pourra imaginer une intégration plus poussée dans les éditeurs avec de l'auto-complétion, ou avec la documentation et la fonction interactive <code>:doc</code>.</p>
<h4 id="toc-command-doc-dans-le-shell">Command <code>:doc</code> dans le shell</h4>
<p>La nouvelle commande <code>:doc</code> permet d’accéder à la documentation des fonctions dans le shell <code>ghci</code>.</p>
<p>Le shell est un des outils les plus importants pour un développeur Haskell, on y passe beaucoup de temps à expérimenter. Jusqu'à présent on pouvait obtenir des informations sur le type :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="kt">:</span><span class="kr">type</span> <span class="n">reverse</span>
<span class="nf">reverse</span> <span class="ow">::</span> <span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="ow">-></span> <span class="p">[</span><span class="n">a</span><span class="p">]</span></code></pre>
<p>On peut aussi maintenant demander la documentation associée:</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="kt">:</span><span class="n">doc</span> <span class="n">reverse</span>
<span class="n">'reverse'</span> <span class="o">@</span><span class="n">xs</span><span class="o">@</span> <span class="n">returns</span> <span class="n">the</span> <span class="n">elements</span> <span class="kr">of</span> <span class="o">@</span><span class="n">xs</span><span class="o">@</span> <span class="kr">in</span> <span class="n">reverse</span> <span class="n">order</span><span class="o">.</span>
<span class="o">@</span><span class="n">xs</span><span class="o">@</span> <span class="n">must</span> <span class="n">be</span> <span class="n">finite</span></code></pre>
<p>Le code Haskell étant documenté par <a href="https://www.haskell.org/haddock/">Haddock</a>, un langage de documentation intégré à Haskell, on peut maintenant accéder à ces informations dans le shell. Le fait d'avoir un point d'entrée standardisé nous laisse imaginer un meilleur support d'outils dans le futur, tel qu'une intégration aux éditeurs de texte.</p>
<p>L'argument <code>-fshow-docs-of-hole-fits</code> devrait permettre une interaction entre la documentation et les "typed holes" du paragraphe précédent en affichant la documentation en face des suggestions.</p>
<h3 id="toc-extension-au-mécanisme-de-programmation-générique">Extension au mécanisme de programmation générique</h3>
<h4 id="toc-derivingvia"><code>DerivingVia</code></h4>
<p>Cette nouvelle extension augmente le mécanisme de dérivation présent dans GHC. Pour bien comprendre cette nouvelle fonctionnalité, il faut faire un petit rappel sur un point clé d'Haskell, les type-<code>class</code> et le mécanisme de dérivation.</p>
<h5 id="toc-notion-dinstance-et-de-class">Notion d'<code>instance</code> et de <code>class</code>
</h5>
<p>En Haskell, une <code>class</code> est une interface et une <code>instance</code> est l’implémentation de celle-ci pour un type. Le vocabulaire n'est pas du tout le même que celui utilisé plus habituellement dans des langages objets.</p>
<p>Par exemple, on peut imaginer la classe <code>Shape</code> :</p>
<pre><code class="haskell"><span class="kr">class</span> <span class="kt">Shape</span> <span class="n">t</span> <span class="kr">where</span>
<span class="n">area</span> <span class="ow">::</span> <span class="n">t</span> <span class="ow">-></span> <span class="kt">Float</span></code></pre>
<p>Qui propose la fonction <code>area</code> qui, appliquée à n'importe quel type implémentant le type-classe <code>Shape</code>, va retourner sa surface. Par exemple :</p>
<pre><code class="haskell"><span class="c1">-- Création de deux types, 'Square' et 'Rectangle'</span>
<span class="kr">data</span> <span class="kt">Square</span> <span class="ow">=</span> <span class="kt">Square</span> <span class="kt">Float</span>
<span class="kr">data</span> <span class="kt">Rectangle</span> <span class="ow">=</span> <span class="kt">Rectangle</span> <span class="kt">Float</span> <span class="kt">Float</span>
<span class="c1">-- Implémentation de 'area' pour les deux</span>
<span class="kr">instance</span> <span class="kt">Shape</span> <span class="kt">Square</span> <span class="kr">where</span>
<span class="n">area</span> <span class="p">(</span><span class="kt">Square</span> <span class="n">side</span><span class="p">)</span> <span class="ow">=</span> <span class="n">side</span> <span class="o">*</span> <span class="n">side</span>
<span class="kr">instance</span> <span class="kt">Shape</span> <span class="kt">Rectangle</span> <span class="kr">where</span>
<span class="n">area</span> <span class="p">(</span><span class="kt">Rectangle</span> <span class="n">a</span> <span class="n">b</span><span class="p">)</span> <span class="ow">=</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span></code></pre>
<p>Avec ici deux types, <code>Square</code> et <code>Rectangle</code> et leur implementation <code>instance</code> de la classe. </p>
<p>On peut donc maintenant écrire des fonctions polymorphiques fonctionnant uniquement sur les instances de <code>Shape</code> :</p>
<pre><code class="haskell"><span class="nf">print_area</span> <span class="n">shape</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">putStrLn</span> <span class="s">"La surface de la forme est :"</span>
<span class="n">print</span> <span class="p">(</span><span class="n">area</span> <span class="n">shape</span><span class="p">)</span></code></pre>
<p>Nous avons vu ici la création d'<code>instance</code>s manuelle.</p>
<h5 id="toc-deriving"><code>deriving</code></h5>
<p>Haskell propose le mécanisme de <code>deriving</code> qui permet de dériver automatiquement des <code>instance</code>s pour nos types. Ce mécanisme a beaucoup évolué et on peut :</p>
<ul>
<li>dériver des comportements proposés par le compilateur ou par une bibliothèque tierce, ici pour le type <code>Point3D</code> nous dérivons les instances <code>Show</code> et <code>Serialize</code> qui permettent d'afficher (avec <code>show</code>) et de sérialiser avec <code>encode</code> / <code>decode</code> :</li>
</ul>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Point3D</span> <span class="ow">=</span> <span class="kt">Point3D</span> <span class="kt">Float</span> <span class="kt">Float</span> <span class="kt">Float</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Serialize</span><span class="p">)</span></code></pre>
<ul>
<li>dériver des comportements d'un type interne si le type n'est qu'une encapsulation du précédent. Par exemple :</li>
</ul>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Counter</span> <span class="ow">=</span> <span class="kt">Counter</span> <span class="kt">Int</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Num</span><span class="p">)</span></code></pre>
<p><code>Counter</code> est maintenant un type qui contient un <code>Int</code>, qui implémente toute la classe <code>Num</code> de façon similaire aux <code>Int</code>, donc il implémente les opérations primaires comme l'addition, la soustraction, etc. Mais ce type n'est pas compatible avec un <code>Int</code>.</p>
<p>C'est très pratique pour faire des interfaces robustes.</p>
<h5 id="toc-derivingvia-1"><code>DerivingVia</code></h5>
<p><code>DerivingVia</code> est une nouvelle extension qui ajoute une nouvelle manière de dériver. On peut imaginer plusieurs types ayant la même représentation interne et pour lesquels on veuille dériver le même comportement. Au lieu de recopier ce comportement plusieurs fois, on peut faire appelle à <code>derive via</code>.</p>
<p>Par exemple :</p>
<pre><code class="haskell"><span class="c1">-- `Normalize` regroupe tous les types pouvant être "normalisés"</span>
<span class="kr">class</span> <span class="kt">Normalize</span> <span class="n">t</span> <span class="kr">where</span>
<span class="n">normalize</span> <span class="ow">::</span> <span class="n">t</span> <span class="ow">-></span> <span class="n">t</span>
<span class="c1">-- Un triplet de points représentant une couleur</span>
<span class="kr">newtype</span> <span class="kt">RGBColor</span> <span class="ow">=</span> <span class="kt">RGBColor</span> <span class="p">(</span><span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">)</span>
<span class="c1">-- Son instance de 'Normalize'</span>
<span class="kr">instance</span> <span class="kt">Normalize</span> <span class="kt">RGBColor</span> <span class="kr">where</span>
<span class="n">normalize</span> <span class="p">(</span><span class="kt">RGBColor</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">))</span> <span class="ow">=</span> <span class="kt">RGBColor</span> <span class="p">(</span><span class="n">a</span> <span class="o">/</span> <span class="n">s</span><span class="p">,</span> <span class="n">b</span> <span class="o">/</span> <span class="n">s</span><span class="p">,</span> <span class="n">c</span> <span class="o">/</span> <span class="n">s</span><span class="p">)</span>
<span class="kr">where</span> <span class="n">s</span> <span class="ow">=</span> <span class="n">sqrt</span> <span class="p">(</span><span class="n">a</span> <span class="o">*</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="o">*</span> <span class="n">b</span> <span class="o">+</span> <span class="n">c</span> <span class="o">*</span> <span class="n">c</span><span class="p">)</span>
<span class="c1">-- Le même travail est à répéter pour ces nouveaux types, similaires. </span>
<span class="c1">-- On va gagner du temps avec `deriving via`</span>
<span class="kr">newtype</span> <span class="kt">Direction</span> <span class="ow">=</span> <span class="kt">Direction</span> <span class="p">(</span><span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">)</span> <span class="kr">deriving</span> <span class="kt">Normalize</span> <span class="n">via</span> <span class="kt">RGBColor</span>
<span class="kr">newtype</span> <span class="kt">Position</span> <span class="ow">=</span> <span class="kt">Position</span> <span class="p">(</span><span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">,</span> <span class="kt">Float</span><span class="p">)</span> <span class="kr">deriving</span> <span class="kt">Normalize</span> <span class="n">via</span> <span class="kt">RGBColor</span></code></pre>
<p>Ce travail permet ainsi une plus grande réutilisation. L'idée étant que souvent des types représentant des choses incompatibles peuvent avoir des fonctionnalités similaires. On pourrait citer l'exemple des monnaies. Un "Dollar" est incompatible avec un "Euro", ainsi l'addition entre les deux n'a pas de sens. Cependant l'addition entre euros est exactement la même que l'addition entre dollars, il est donc dommage d’implémenter celle-ci pour chaque monnaie.</p>
<h3 id="toc-greffons-de-compilation">Greffons de compilation</h3>
<p>GHC propose une infrastructure de greffons (<em>plugins</em>) permettant aux utilisateurs d'étendre le compilateur sans avoir à mettre les mains dans un code complexe et sans avoir à compiler une nouvelle version du compilateur complet.</p>
<p>On trouve trois types de greffons :</p>
<ul>
<li>Les greffons d'optimisation, qui permettent d'étendre la phase de génération de code. Par exemple, <a href="https://github.com/mikeizbicki/HerbiePlugin">Herbie</a> permet de réécrire des calculs afin de les rendre numériquement plus stables. </li>
<li>Les greffons de validation de type vont étendre la phase de validation des types. Par exemple, <a href="https://github.com/Divesh-Otwani/the-thoralf-plugin">thoralf</a> permet de sous-traiter à un solveur SMT des preuves de type. Cela permet d'exprimer assez simplement des preuves plus complexes.</li>
</ul>
<h4 id="toc-les-greffons-sources">Les greffons sources</h4>
<p>Introduits par GHC 8.6, ils permettent à un développeur d'observer le code source après analyse syntaxique afin de transformer celui-ci ou de simplement le passer en parallèle à une tâche annexe.</p>
<p>Ils ont comme intérêt, à mon sens, de permettre de réaliser en parallèle de la compilation d'autres tâches ayant besoin de l'analyse syntaxique et de réutiliser celle-ci. Lorsque je compile mon code, je génère aussi la documentation, je passe un linter dessus, je passe un formateur dessus et je génère des informations comme une base de données des fonctions définies, un graphique des modules, etc. Chacun de ces outils demande actuellement une nouvelle phase d'analyse syntaxique réalisée par un outil externe qui implémente souvent sa propre version limitée et défectueuse du parseur. Pouvoir réaliser ces tâches annexes pendant la compilation principale serait donc un gain en temps et en qualité.</p>
<p>GHC 8.6 n'etait pas encore sorti que de nombreux projets s’essayaient à la tache :</p>
<ul>
<li>
<a href="http://mpickering.github.io//posts/2018-08-09-source-plugin-graphmod.html">Une réécriture de <code>graphmod</code></a> permet de générer des graphiques représentant les dépendances entre modules Haskell.</li>
<li>
<a href="https://github.com/ocharles/hlint-source-plugin">hlint-source-plugin</a> permet de réaliser un <a href="https://en.wikipedia.org/wiki/Lint_(software)">lint</a>, c'est à dire une analyse du style du code.</li>
<li>
<a href="http://oleg.fi/gists/posts/2018-07-06-idiom-brackets-via-source-pluging.html">idiom-bracket</a> L'auteur a judicieusement remarqué qu'il est rare de mettre entre parentheses des listes : on va rarement écrire <code>([f,g,h])</code> car les parenthèses semblent redondantes. L'auteur "vole" donc cette syntaxe pour exprimer une toute nouvelle construction.</li>
</ul>
<h3 id="toc-quantified-constraints">Quantified Constraints</h3>
<p>Ceci est une nouveauté phare de cette version qui simplifie les contraintes lors de la définition d'une instance.</p>
<p>Prenons un exemple simple pour la mise en contexte. Si on veut définir une instance de <code>Show</code> pour une liste de <code>a</code>, on écrira :</p>
<pre><code class="haskell"><span class="kr">instance</span> <span class="kt">Show</span> <span class="n">a</span> <span class="ow">=></span> <span class="kt">Show</span> <span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">show</span> <span class="ow">=</span> <span class="o">...</span></code></pre>
<p>Ce qui veut littéralement dire que <code>[a]</code> admet une instance de <code>Show</code> si et seulement si <code>a</code> admet une instance de <code>Show</code>. Ou, dit autrement, on peut afficher une liste de <code>a</code> si on sait afficher <code>a</code>. Cela a du sens, on va définir l'affichage pour la liste, en déléguant l'affichage de chaque élément aux fonctions déjà définies pour ces éléments.</p>
<p>Prenons l'exemple plus élaboré d'un type <code>User</code> paramétré :</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">User</span> <span class="n">f</span> <span class="ow">=</span> <span class="kt">User</span>
<span class="p">{</span> <span class="n">nom</span> <span class="ow">::</span> <span class="n">f</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">age</span> <span class="ow">::</span> <span class="n">f</span> <span class="kt">Int</span>
<span class="p">,</span> <span class="n">superUtilisateur</span> <span class="ow">::</span> <span class="n">f</span> <span class="kt">Bool</span>
<span class="p">}</span></code></pre>
<p>Le paramétrage par <code>f</code> permet d'utiliser le même type pour representer de nombreuses variations :</p>
<ul>
<li>
<code>User Identity</code> est, à un détail près, un record <code>User</code> sans le paramétrage, il contient donc une <code>String</code>, un <code>Int</code> et un <code>Bool</code>.</li>
<li>
<code>User Maybe</code> est un <code>User</code> dont tous les champs sont des <code>Maybe</code>, c'est à dire qu'ils peuvent avoir une valeur ou pas. C'est pratique en première étape d'un système de validation, par exemple, on pourrait imaginer une fonction de validation <code>validation :: User Maybe -> Maybe (User Identity)</code>.</li>
<li>
<code>User []</code> contient une liste pour chaque champs. C'est pratique pour faire du <a href="https://en.wikipedia.org/wiki/AOS_and_SOA">Structure of Array</a>. On pourrait même imaginer une fonction <code>toArray :: [User Identity] -> User []</code> et <code>fromArray :: User [] -> [User Identity]</code> permettant de passer d'une liste d'utilisateur à un utilisateur contenant des listes et inversement.</li>
</ul>
<p>Vous pouvez lire cet article sur les <a href="https://www.benjamin.pizza/posts/2017-12-15-functor-functors.html">functor functors</a> pour plus de détails sur cet usage.</p>
<p>Il n'est pas aisé de dériver des instances pour <code>User</code>. En effet, pour pouvoir être affiché, un <code>User f</code> doit pouvoir afficher chaque champs, c'est-à-dire :</p>
<ul>
<li>
<code>f String</code> pour le <code>nom</code>
</li>
<li>
<code>f Int</code> pour l'<code>age</code>
</li>
<li>
<code>f Bool</code> pour le <code>superUtilisateur</code>
</li>
</ul>
<p>Ainsi, pour dériver automatiquement l'instance de <code>Show</code>, il faudrait écrire :</p>
<pre><code class="haskell"><span class="kr">deriving</span> <span class="kr">instance</span>
<span class="p">(</span> <span class="kt">Show</span> <span class="p">(</span><span class="n">f</span> <span class="kt">String</span><span class="p">)</span>
<span class="p">,</span> <span class="kt">Show</span> <span class="p">(</span><span class="n">f</span> <span class="kt">Int</span><span class="p">)</span>
<span class="p">,</span> <span class="kt">Show</span> <span class="p">(</span><span class="n">f</span> <span class="kt">Bool</span><span class="p">)</span>
<span class="p">)</span> <span class="ow">=></span> <span class="kt">Show</span> <span class="p">(</span><span class="kt">User</span> <span class="n">f</span><span class="p">)</span></code></pre>
<p>Cette écriture est lourde, source d'erreur, peu générique et évolue difficilement. Par exemple, si demain j'ajoute un champs <code>droitUtilisateur :: f Droits</code>, je devrais ajouter une clause <code>Show (f Droits)</code> à toutes mes instances.</p>
<p>Ce qui serait agréable c'est de pouvoir dire que si on peut afficher un <code>f b</code>, alors on peut afficher un <code>User f</code>. C'est ce qui est maintenant possible avec <code>QuantifiedConstraints</code> :</p>
<pre><code class="haskell"><span class="kr">deriving</span> <span class="kr">instance</span> <span class="p">(</span><span class="n">forall</span> <span class="n">b</span><span class="o">.</span> <span class="kt">Show</span> <span class="n">b</span> <span class="ow">=></span> <span class="kt">Show</span> <span class="p">(</span><span class="n">f</span> <span class="n">b</span><span class="p">))</span> <span class="ow">=></span> <span class="kt">Show</span> <span class="p">(</span><span class="kt">User</span> <span class="n">f</span><span class="p">)</span></code></pre>
<p>Notez le <code>forall b. Show b => Show (f b)</code> qui signifie que quel que soit <code>b</code>, si on peut afficher <code>b</code>, alors si on sait afficher <code>f b</code> alors on sait afficher <code>User f</code>.</p>
<p>Cette nouvelle extension apporte donc une très grosse simplification de cas d'instances complexes sur des types paramétriques.</p>
<h3 id="toc-plus-de-tests-dexhaustivité">Plus de tests d’exhaustivité</h3>
<p>GHC réalise des tests d'exhaustivité sur les <code>case</code>s et les appels de fonctions. Par exemple, le code suivant :</p>
<pre><code class="haskell"><span class="kr">data</span> <span class="kt">Couleur</span> <span class="ow">=</span> <span class="kt">Rouge</span> <span class="o">|</span> <span class="kt">Vert</span> <span class="o">|</span> <span class="kt">Bleu</span>
<span class="nf">phraseIdiote</span> <span class="kt">Rouge</span> <span class="ow">=</span> <span class="s">"La lune est rouge, du sang a coulé cette nuit"</span>
<span class="nf">phraseIdiote</span> <span class="kt">Vert</span> <span class="ow">=</span> <span class="s">"Les martiens sont tous verts"</span></code></pre>
<p>Il manque le cas pour <code>Bleu</code> et le compilateur va s'en rendre compte:</p>
<pre><code><interactive>:16:1: warning: [-Wincomplete-patterns]
Pattern match(es) are non-exhaustive
In an equation for ‘phraseIdiote’: Patterns not matched: Bleu
</code></pre>
<p>Cette capacité est maintenant étendue aux <em>guard</em> et à la syntaxe if étendue. Par exemple, le code suivant ne gère pas le cas ou <code>b</code> est <code>False</code> :</p>
<pre><code class="haskell"><span class="c1">-- si 'b' est True, retourne '1', sinon... ?</span>
<span class="nf">foo</span> <span class="n">b</span> <span class="ow">=</span> <span class="kr">if</span> <span class="o">|</span> <span class="n">b</span> <span class="ow">-></span> <span class="mi">1</span></code></pre>
<p>Ce code va maintenant générer un warning lors de la compilation.</p>
<h3 id="toc-plus-de-constant-folding">Plus de constant folding</h3>
<p>GHC effectue maintenant plus de <em>constant folding</em> lors de la compilation. </p>
<p>Par exemple, le code <code>(5 + 4*x) - (3*x + 2)</code> est équivalent à <code>3 + x</code> tout en coûtant deux multiplications, une addition et une soustraction de moins.</p>
<h3 id="toc-extension-de-partialtypesignatures">Extension de <code>PartialTypeSignatures</code>
</h3>
<p><code>PartialTypeSignatures</code> est permet de ne pas expliciter tous les types dans une signature.</p>
<p>On rappelle qu'en Haskell, les types sont inférés, donc il n'est presque jamais nécessaire de mettre de signature aux fonctions. Cependant cela permet de temps en temps de rendre le code plus lisible, de documenter ou de rendre la signature moins polymorphique.</p>
<p><code>PartialTypeSignatures</code> permet de ne préciser que les types necessaires, les autres étant déduits via l'inférence de type.</p>
<p>C'est particulièrement utile avec certains types complexes ayant beaucoup de contraintes. Par exemple, la signature suivante qui cherche une valeur dans une liste:</p>
<pre><code class="haskell"><span class="c1">-- 'Eq v' signifie que les valeurs dans la liste doivent pouvoir être comparées </span>
<span class="nf">search</span> <span class="ow">::</span> <span class="kt">Eq</span> <span class="n">v</span> <span class="ow">=></span> <span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="ow">-></span> <span class="n">v</span> <span class="ow">-></span> <span class="kt">Bool</span></code></pre>
<p>Pourrait être simplifiée en:</p>
<pre><code class="haskell"><span class="kr">_</span> <span class="ow">=></span> <span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="ow">-></span> <span class="n">v</span> <span class="ow">-></span> <span class="kt">Bool</span></code></pre>
<p>C'est très pratique lors de l'usage de bibliothèques ayant de nombreuses contraintes.</p>
<p>Depuis GHC 8.6, on peut maintenant utiliser le symbole <code>_</code> dans plus de contextes, et tout particulièrement lors de la définition d'une instance, par exemple <code>deriving instance Show v => Show [v]</code> qui dérive l'affichage de toute liste dont le type interne dérive l'affichage, pourra être écrit <code>_ => Show [v]</code>.</p>
<h3 id="toc-heap-view">Heap View</h3>
<p><a href="http://hackage.haskell.org/package/ghc-heap-view">http://hackage.haskell.org/package/ghc-heap-view</a> est maintenant integré à ghci (le shell) et permet d'observer l'organisation en mémoire des valeurs manipulées. <a href="https://patrickdoc.github.io/heap-view.html">https://patrickdoc.github.io/heap-view.html</a> est une bonne introduction.</p>
<p>Par exemple :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="kr">import</span> <span class="nn">GHC.Exts.Heap</span>
<span class="o">>>></span> <span class="n">p</span> <span class="ow">=</span> <span class="n">sum</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="ow">::</span><span class="kt">Int</span><span class="p">]</span></code></pre>
<p>Cette valeur n'est pas encore évaluée :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">getClosureData</span> <span class="n">p</span>
<span class="kt">APClosure</span> <span class="p">{</span><span class="n">info</span> <span class="ow">=</span> <span class="kt">StgInfoTable</span> <span class="p">{</span><span class="n">entry</span> <span class="ow">=</span> <span class="kt">Nothing</span><span class="p">,</span> <span class="n">ptrs</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">nptrs</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">tipe</span> <span class="ow">=</span> <span class="kt">AP</span><span class="p">,</span> <span class="n">srtlen</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">code</span> <span class="ow">=</span> <span class="kt">Nothing</span><span class="p">},</span> <span class="n">arity</span> <span class="ow">=</span> <span class="mi">26827633</span><span class="p">,</span> <span class="n">n_args</span> <span class="ow">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">fun</span> <span class="ow">=</span> <span class="mh">0x0000004201996250</span><span class="p">,</span> <span class="n">payload</span> <span class="ow">=</span> <span class="kt">[]</span><span class="p">}</span></code></pre>
<p>Chaque objet en mémoire en Haskell commence par une table d'information <code>info</code> qui contient des informations pour le garbage collector et pour l'évaluation. Ici on peut voir que le <code>tipe</code> est <code>AP</code>, une fonction à appliquer.</p>
<p>Nous pouvons forcer l'évaluation de la valeur :</p>
<pre><code>>>> p
55
>>> getClosureData p
ConstrClosure {info = StgInfoTable {entry = Nothing, ptrs = 0, nptrs = 1, tipe = CONSTR_0_1, srtlen = 0, code = Nothing}, ptrArgs = [], dataArgs = [55], pkg = "ghc-prim", modl = "GHC.Types", name = "I#"}
</code></pre>
<p>Ici, on a une <code>ConstrClosure</code>, c'est à dire un constructeur, un type concret. <code>55</code> apparaît bien dans la liste des arguments et on peut voir que le constructeur est <code>I#</code>, le wrapper autour des entiers machine.</p>
<p>Cela sera sans doute un très bon outil pour comprendre les comportements en mémoire de certains algorithmes.</p>
<p>On rappelle que le shell ghci vient avec <code>:sprint</code> qui permet d'observer moins de choses, mais au moins l'évaluation paresseuse, les êléments non évalués étant remplacés par <code>_</code> :</p>
<p>Création d'une liste <code>l</code> contenant des chaines, tout est paresseux, d'ou <code>l = _</code> :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,</span> <span class="s">"Youpi"</span><span class="p">,</span> <span class="s">"Yoda"</span><span class="p">,</span> <span class="s">"You"</span><span class="p">,</span> <span class="s">"Are"</span><span class="p">,</span> <span class="s">"Beautiful"</span><span class="p">]</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="kr">_</span></code></pre>
<p>Récupérer le premier élément provoque l'évaluation de seulement celui-ci :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">head</span> <span class="n">l</span>
<span class="s">"Hello"</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="s">"Hello"</span> <span class="kt">:</span> <span class="kr">_</span></code></pre>
<p>Calculer la longueur de la liste provoque l'évaluation de celle-ci, mais pas des sous éléments. Le premier est toujours évalué.</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">length</span> <span class="n">l</span>
<span class="mi">6</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,</span><span class="kr">_</span><span class="p">,</span><span class="kr">_</span><span class="p">,</span><span class="kr">_</span><span class="p">,</span><span class="kr">_</span><span class="p">,</span><span class="kr">_</span><span class="p">]</span></code></pre>
<p><code>map head l</code> va récupérer le premier caractère de chacun des éléments de la liste, ce qui va provoque l'évaluation partielle de ceux-ci :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">map</span> <span class="n">head</span> <span class="n">l</span>
<span class="s">"HYYYAB"</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'A'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'B'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">)]</span></code></pre>
<p>La recherche d'un élément <code>=="You"</code> est passionnante. "Hello" est déjà évalué, il ne change rien. <code>"Youpi"</code> doit être évalué jusqu'au <code>i</code> pour réaliser que ce n'est pas la bonne valeur. <code>"Yoda"</code> pareil. <code>"You"</code> est totalement évalué et l’exécution s'arrête :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">any</span> <span class="p">(</span><span class="o">==</span><span class="s">"You"</span><span class="p">)</span> <span class="n">l</span>
<span class="kt">True</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'u'</span> <span class="kt">:</span> <span class="sc">'p'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'d'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),</span>
<span class="s">"You"</span><span class="p">,(</span><span class="sc">'A'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'B'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">)]</span></code></pre>
<p>La création d'une liste paresseuse qui contient la longueur de tous les éléments de la liste ne change rien :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">l2</span> <span class="ow">=</span> <span class="n">map</span> <span class="n">length</span> <span class="n">l</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'u'</span> <span class="kt">:</span> <span class="sc">'p'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'d'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),</span>
<span class="s">"You"</span><span class="p">,(</span><span class="sc">'A'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'B'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">)]</span></code></pre>
<p>Mais l'observation du dernier élément force l'évaluation du dernier élément de la première liste :</p>
<pre><code class="haskell"><span class="o">>>></span> <span class="n">last</span> <span class="n">l2</span>
<span class="mi">9</span>
<span class="o">>>></span> <span class="kt">:</span><span class="n">sprint</span> <span class="n">l</span>
<span class="nf">l</span> <span class="ow">=</span> <span class="p">[</span><span class="s">"Hello"</span><span class="p">,(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'u'</span> <span class="kt">:</span> <span class="sc">'p'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),(</span><span class="sc">'Y'</span> <span class="kt">:</span> <span class="sc">'o'</span> <span class="kt">:</span> <span class="sc">'d'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),</span>
<span class="s">"You"</span><span class="p">,(</span><span class="sc">'A'</span> <span class="kt">:</span> <span class="kr">_</span><span class="p">),</span><span class="s">"Beautiful"</span><span class="p">]</span></code></pre>
<p>L'évaluation paresseuse c'est formidable ;)</p>
<h3 id="toc-monadfail">MonadFail</h3>
<p>Jusqu'à présent, la classe <code>Monad</code> incluait une fonction <code>fail</code> qui était utilisée en cas d'erreur de pattern dans une <code>do</code> notation. Par exemple, le code suivant, qui fonctionne pour toutes les <code>Monad</code>s :</p>
<pre><code class="haskell"><span class="nf">f</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kt">Just</span> <span class="n">x</span> <span class="ow"><-</span> <span class="n">anOperation</span>
<span class="n">pure</span> <span class="n">x</span></code></pre>
<p>Peut échouer, car il n'y a aucune garantie que la valeur matchée par <code>Just x</code> ne sera pas <code>Nothing</code>. Ainsi <code>Monad</code> incluait une fonction <code>fail</code> pour permettre de surcharger ce comportement.</p>
<p>Il y a des cas où l'implementation de <code>fail</code> a du sens. Dans les autres cas, <code>fail</code> est implementé comme une exception. Ce qui veut dire que du code qui a l'air parfaitement anodin peut en fait planter lors de l'exécution, ce qui est inadmissible (avis d'un auteur ;) en Haskell où on préfère les erreurs statiques que les erreurs d'exécution.</p>
<p>Il a donc été décidé de déplacer <code>fail</code> de la classe <code>Monad</code> dans une super classe de <code>Monad</code>, appelée <code>MonadFail</code>.</p>
<p>Ainsi, le code suivant, qui ne peut pas échouer :</p>
<pre><code class="haskell"><span class="nf">f</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">x</span> <span class="ow"><-</span> <span class="n">anOperation</span>
<span class="n">pure</span> <span class="n">x</span></code></pre>
<p>Aura comme type <code>Monad m => m t</code> et est garanti sans exception.</p>
<p>Alors que le code suivant, qui peut échouer :</p>
<pre><code class="haskell"><span class="nf">f'</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kt">Just</span> <span class="n">x</span> <span class="ow"><-</span> <span class="n">anOperation</span>
<span class="n">pure</span> <span class="n">x</span></code></pre>
<p>Aura comme type <code>MonadFail m -> m t</code> et le comportement en cas d'erreur dépend de la <code>Monad</code> utilisée. Par exemple :</p>
<ul>
<li>La monad de liste va ignorer l'élément associé à cet echec</li>
<li>Une opération d'<code>IO</code> va réaliser une exception</li>
<li>Une monad type <code>Maybe</code> va renvoyer <code>Nothing</code>.</li>
</ul>
<p>La différence permet de faire apparaître la notion d'erreur possible dans le type. Bien sûr, l'utilisation de <code>f'</code> dans un contexte <code>Monad</code> (et non <code>MonadError</code>) provoquera une erreur de compilation.</p>
<p>L'honneur est donc sauf, moins d'erreur à l'exécution, plus d'erreurs à la compilation.</p>
<h3 id="toc-starisstar"><code>StarIsStar</code></h3>
<p><code>StarIsStar</code> est une nouvelle extension activée par défaut (et désactivable avec <code>NoStarIsStar</code>) qui permet d'utiliser <code>*</code> comme symbole pour <code>Type</code> : le type de la plupart des types. Le but est à terme de désactiver cette extension afin de libérer le symbole <code>*</code> pour qu'il puisse être utilisé comme symbole de multiplication au niveau du type.</p>
<p>Ce n'est pas clair ? Il faut voir un type comme un ensemble de valeurs possibles. Par exemple, <code>Int</code> est l'ensemble des valeurs <code>0, 1, 2, 3, ..., -1, -2, -3, ...</code>. Il existe aussi un ensemble des types, qui s'appelle <code>Type</code> (ou <code>*</code> avec <code>StarIsStar</code>), cet ensemble contient <code>Int</code>, <code>Double</code>, … et tous les types que vous allez créer.</p>
<p>Cela a de l’intérêt car on peut aussi définir des types qui ne sont pas dans cet ensemble <code>Type</code>, c'est pratique pour faire des opérations au niveau du type.</p>
<p>Et le problème c'est que, au niveau du type, n'importe quel symbole Haskell peut être utilisé pour implémenter un opérateur, sauf <code>*</code> qui est un cas particulier pour représenter <code>Type</code>, d'où la motivation pour faire disparaître celui-ci afin de simplifier le compilateur et permettre son utilisation en tant qu'opérateur.</p>
<h2 id="toc-analyser-le-journal-des-modifications-de-linuxfrorg">Analyser le journal des modifications de LinuxFr.org</h2>
<p>Comme souvent avec les dépêches sur une nouvelle sortie de GHC, celle-ci en profite pour montrer un exemple d'utilisation de Haskell dans la nature ! Il s'agit d'analyser syntaxiquement et de stocker de façon personnalisée les <a href="//linuxfr.org/changelog">changelogs</a> de ce site où une entrée typique est générée sous cette forme :</p>
<pre><code>commit ac34cc5d787f569a237d16a2ec8be994c2a37acc
Author: Bruno Michel <bmichel@menfin.info>
Date: Fri Aug 3 15:58:34 2018 +0200
Revert putting the phare on top of the logo and sidebar
</code></pre>
<p>Un modèle syntaxique correspondant en <a href="https://fr.wikipedia.org/wiki/Forme_de_Backus-Naur">BNF</a> où les chevrons représentent les terminaux :</p>
<pre><code>Eol ::= <fin_de_ligne>
Lettre ::= <lettre_minuscule> | <lettre_majuscule>
Condensat ::= <lettre_minuscule> | <chiffre> | Eol
Espace ::= <simple_espace> | <tabulation>
Début_e ::= <<>
Email ::= Début_e | Lettre | <@> | <.> | <-> | <_> | Fin_e
Fin_e ::= <>>
Author ::= Lettre | Espace | Email | Eol
Date ::= Lettre | <chiffre> | <:> | <+> | <-> | Espace | Eol
Message ::= Lettre | Espace | <,> | <-> | <.> | </> | <:> | Eol
Log ::= Condensat | Author | Date | Message | <fin_de_fichier>
</code></pre>
<p>Dans cette grammaire, on remarque d'emblée que tous les symboles, terminaux ou non, sont traités de façon <em>ad hoc</em>, même pour les cas ayant des standards les définissant très précisément : adresses électroniques, condensats et dates notamment. Cela vient d'une volonté de simplifier au maximum l'exemple. La suite de cette section montrera comment s'y prendre en Haskell pour exprimer ce que <code>Log</code> exprime en BNF.</p>
<p>Au sein de l'écosystème Haskell, il existe plusieurs bibliothèques destinées à traiter des grammaires comme celle-ci dont un certain nombre peuvent être regroupées sous l'appellation <em>“Parser Combinators”</em>. Ainsi de <a href="http://hackage.haskell.org/package/parsec">parsec</a> qui a longtemps été la vitrine de telles bibliothèques, du fait d'un nom trop bien choisi entre autres choses (le <code>c</code> étant pour … “combinators”). Il y a bien sûr d'autres bibliothèques qui s'y prennent autrement, à l'instar de <code>happy</code> et <code>alex</code> qui sont utilisées pour gérer GHC lui-même. Notre exemple utilisera <a href="http://hackage.haskell.org/package/megaparsec">megaparsec</a>, une alternative de <code>parsec</code>. Dans les paragraphes qui vont suivre, les blocs de code seront tirés d'un fichier source appelé <code>Depeche.hs</code>. Voici l'en-tête qui déclare les modules requis :</p>
<pre><code class="Haskell"><span class="kr">module</span> <span class="nn">Depeche</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Data.Either</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="nn">Data.Foldable</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="nn">Data.Maybe</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="nn">Data.Void</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="nn">Prelude</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="nn">Text.Megaparsec</span> <span class="c1">-- megaparsec</span>
<span class="kr">import</span> <span class="nn">Text.Megaparsec.Char</span> <span class="c1">-- megaparsec</span>
<span class="kr">import</span> <span class="nn">Text.Read</span> <span class="c1">-- base</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Text.Megaparsec.Char.Lexer</span> <span class="k">as</span> <span class="n">Lx</span> <span class="c1">-- megaparsec</span>
<span class="kr">import</span> <span class="nn">Data.Time</span> <span class="c1">-- time</span></code></pre>
<p>Notons qu'à part <code>megaparsec</code> et <code>time</code>, une seule bibliothèque livrée avec GHC fournit les modules requis par cet exemple. La dernière déclaration est dite « qualifiée » dans le sens où toute entité venant de ce module devra être précédée soit du nom de celui-ci, soit d'un préfixe à préciser (ici <code>Lx</code>) ; c'est pour éviter la collision entre les espaces de noms de ce module et du reste des modules de <code>megaparsec</code> qui exposent des entités homonymes. Définissons ensuite les types représentant une entrée du journal :</p>
<pre><code class="Haskell"><span class="kr">data</span> <span class="kt">Author</span> <span class="ow">=</span> <span class="kt">MkAuthor</span>
<span class="p">{</span> <span class="n">firstName</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">lastName</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">emailAddr</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="kt">Show</span>
<span class="kr">data</span> <span class="kt">ChangelogEntry</span> <span class="ow">=</span> <span class="kt">MkEntry</span>
<span class="p">{</span> <span class="n">commitHash</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">commitAuthor</span> <span class="ow">::</span> <span class="kt">Author</span>
<span class="p">,</span> <span class="n">commitDate</span> <span class="ow">::</span> <span class="kt">UTCTime</span>
<span class="p">,</span> <span class="n">commitMsg</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="kt">Show</span></code></pre>
<p><code>Author</code> est un enregistrement à trois champs, chacun étant une chaîne de caractères. Une valeur de ce type est créée avec la fonction <code>MkAuthor</code>, automatiquement générée par le compilateur, et <code>deriving Show</code> permet de generer automatiquement la fonction <code>show</code> d'affichage. Une description similaire est applicable à <code>ChangelogEntry</code>. <code>UTCTime</code> provient de <code>Data.Time</code> et représente une date.</p>
<p>Passons ensuite aux parseurs qui auront tous ce format :</p>
<pre><code class="Haskell"><span class="kr">type</span> <span class="kt">Parser</span> <span class="ow">=</span> <span class="kt">Parsec</span> <span class="kt">Void</span> <span class="kt">String</span></code></pre>
<p>Ainsi, un parseur est un type composite qui met ensemble un type <code>Parsec</code>, ne se préoccupe pas de personnaliser la gestion des erreurs (<code>Void</code>) et prend en entrée une chaîne de caractères de type <code>String</code>. Tout ça est tout bonnement synonyme de <code>Parser</code> pour nous. <code>Parser a</code> est donc le type qui décrit un parseur d'une valeur de type <code>a</code>. Viennent ensuite les parseurs proprement dit, en commençant par les « sous-parseurs » :</p>
<pre><code class="Haskell"><span class="nf">mixAlnumPunc</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Char</span><span class="p">]</span> <span class="ow">-></span> <span class="kt">Parser</span> <span class="kt">String</span>
<span class="nf">mixAlnumPunc</span> <span class="n">xs</span> <span class="ow">=</span> <span class="n">some</span> <span class="p">(</span><span class="n">alphaNumChar</span> <span class="o"><|></span> <span class="n">oneOf</span> <span class="n">xs</span><span class="p">)</span>
<span class="nf">parseEmail</span><span class="p">,</span> <span class="n">parseHash</span><span class="p">,</span> <span class="n">parseLetters</span><span class="p">,</span> <span class="n">parseMessage</span> <span class="ow">::</span> <span class="kt">Parser</span> <span class="kt">String</span>
<span class="nf">parseEmail</span> <span class="ow">=</span> <span class="n">between</span>
<span class="p">(</span><span class="n">single</span> <span class="sc">'<'</span><span class="p">)</span>
<span class="p">(</span><span class="n">single</span> <span class="sc">'>'</span><span class="p">)</span>
<span class="p">(</span><span class="n">mixAlnumPunc</span> <span class="s">"@.-_"</span><span class="p">)</span>
<span class="nf">parseHash</span> <span class="ow">=</span> <span class="n">many</span> <span class="p">(</span><span class="n">lowerChar</span> <span class="o"><|></span> <span class="n">digitChar</span><span class="p">)</span>
<span class="nf">parseLetters</span> <span class="ow">=</span> <span class="n">many</span> <span class="n">letterChar</span>
<span class="nf">parseMessage</span> <span class="ow">=</span> <span class="n">mixAlnumPunc</span> <span class="s">" ,-./:"</span></code></pre>
<p>Cet extrait ressemble furieusement à la grammaire de départ ! Veut-on des <code>Lettre ::= <lettre_minuscule> | <lettre_majuscule></code> ? Tient : <code>many (lowerChar <|> upperChar)</code> ! Ou alors veut-on un tiret ou un espace souligné, <code>Tiret_ou_blanc_souligné ::= <-> | <_></code> ? Et bien, voilà : <code>single '-' | single '_'</code> ! Bref, avec les combineurs de parseurs, on définit les éléments syntaxiques d'une manière très proche des notations usuelles.</p>
<p>Il reste maintenant à mettre ensemble ces petites briques pour bâtir le parseur qui nous intéresse. Si le ciment/sable/boue/gravier joue le rôle de liaison entre plusieurs briques, les différents caractères d'espacement jouent un rôle similaire lorsqu'il s'agit d'assembler des éléments syntaxiques dans notre grammaire. Le sous-parseur suivant permet de passer outre tout espace éventuel avant de passer la main à un autre parseur, ceci afin de ne garder que les briques :</p>
<pre><code class="Haskell"><span class="nf">ignoreBlanks</span> <span class="ow">::</span> <span class="kt">Parser</span> <span class="n">a</span> <span class="ow">-></span> <span class="kt">Parser</span> <span class="n">a</span>
<span class="nf">ignoreBlanks</span> <span class="ow">=</span> <span class="kt">Lx</span><span class="o">.</span><span class="n">lexeme</span> <span class="p">(</span><span class="kt">Lx</span><span class="o">.</span><span class="n">space</span> <span class="n">space1</span> <span class="n">empty</span> <span class="n">empty</span><span class="p">)</span></code></pre>
<p><code>Lx.lexeme</code> est une fonction permettant de décrire un parseur d'espace blanc. Elle peut aussi traiter les commentaires, ce que nous ignorons ici avec <code>empty</code>.</p>
<p>Alors, où est le principal parseur ?</p>
<pre><code class="Haskell"><span class="nf">parseEntry</span> <span class="ow">::</span> <span class="kt">Parser</span> <span class="kt">ChangelogEntry</span>
<span class="nf">parseEntry</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="p">(</span><span class="n">string</span> <span class="s">"commit"</span><span class="p">)</span>
<span class="n">theHash</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseHash</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="p">(</span><span class="n">string</span> <span class="s">"Author:"</span><span class="p">)</span>
<span class="n">theFirstName</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseLetters</span>
<span class="n">theLastName</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseLetters</span>
<span class="n">theEmail</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseEmail</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="p">(</span><span class="n">string</span> <span class="s">"Date:"</span><span class="p">)</span>
<span class="n">theDate</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseUTC</span> <span class="c1">-- Voir plus bas</span>
<span class="n">theMessage</span> <span class="ow"><-</span> <span class="n">ignoreBlanks</span> <span class="n">parseMessage</span>
<span class="n">pure</span> <span class="o">$</span> <span class="kt">MkEntry</span>
<span class="n">theHash</span> <span class="p">(</span><span class="kt">MkAuthor</span> <span class="n">theFirstName</span> <span class="n">theLastName</span> <span class="n">theEmail</span><span class="p">)</span>
<span class="n">theDate</span> <span class="n">theMessage</span></code></pre>
<p>Extraire des composants syntaxiques dans le changelog revient dans notre cas à y aller méthodiquement de gauche à droite et de haut en bas, une étape donnée se déroulant en commençant exactement là où les précédentes étapes se sont arrêtées. La notation <code>do … ← …</code> sert à séquencer les transitions pour faire intervenir un sous-parseur à chaque fois. Là où le résultat intermédiaire est inutile, il est immédiatement abandonné en l'envoyant dans le joker <code>_</code>. À la fin du parsing, tous les éléments sont réunis pour invoquer le constructeur <code>MkEntry</code>.</p>
<p>Pour terminer, voici une utilisation de notre parseur :</p>
<pre><code class="Haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">fromStdIn</span> <span class="ow"><-</span> <span class="n">getContents</span>
<span class="n">print</span> <span class="o">$</span>
<span class="n">find</span> <span class="p">(</span><span class="nf">\</span> <span class="n">x</span> <span class="ow">-></span> <span class="n">commitHash</span> <span class="n">x</span> <span class="o">==</span> <span class="s">"a9bfdd7b21320ba18497519f4e2e471d1529c448"</span><span class="p">)</span> <span class="o">$</span>
<span class="n">fromRight</span> <span class="kt">[]</span> <span class="o">$</span>
<span class="n">runParser</span> <span class="p">(</span><span class="n">someTill</span> <span class="n">parseEntry</span> <span class="n">eof</span><span class="p">)</span> <span class="s">"Changelog"</span> <span class="n">fromStdIn</span></code></pre>
<p>Décryptons un peu ce code condensé :</p>
<ul>
<li>
<code>someTill parseEntry eof</code> est un parseur qui utilise <code>parseEntry</code> plusieurs fois jusqu'à trouver une fin de fichier <code>eof</code>.</li>
<li>
<code>runParser p "Changelog" fromStdIn</code> applique le parseur <code>p</code> sur l'entrée <code>fromStdIn</code> qui aura le nom de <code>Changelog</code> dans les messages d'erreurs.</li>
<li>Un parseur peut échouer et renvoyer <code>Left unMessageD'erreur</code> ou <code>Right leResultat</code>. La fonction <code>fromRight</code> nous permet d'ignorer l'échec et de le remplacer par une liste vide <code>[]</code>.</li>
<li>
<code>find</code> va chercher dans cette liste une entrée avec un numéro de commit précis.</li>
</ul>
<p>Pour compiler et expérimenter avec le programme dans le Shell, faire :</p>
<pre><code class="Bash">ghc Depeche.hs
./Depeche < <span class="s1">'linuxfr_changelog'</span></code></pre>
<p>On pourra aussi l’exécuter directement sans compilation, ce code n'étant pas très demandeur en terme de ressource, il s’exécutera sans souci en mode interprété avec <code>runhaskell Depeche.hs < linuxfr_changelog</code>.</p>
<p>En conclusion, à partir d'une grammaire utilisant les notations habituelles comme BNF, les bibliothèques comme <code>megaparsec</code> permettent de définir confortablement des analyseurs syntaxiques, le confort résidant dans la similitude entre leur syntaxe à la fois avec des syntaxes comme BNF et du langage Haskell lui-même.</p>
<p><strong>Addendum</strong><br>
Le parsing des dates utilise les fonctionnalités de parsing de la libraire <code>Data.Time</code>. En voici le code :</p>
<pre><code class="Haskell"><span class="nf">dateFormat</span> <span class="ow">=</span> <span class="s">"%a %b %e %H:%M:%S %Y %Z"</span>
<span class="nf">parseUTC</span> <span class="ow">::</span> <span class="kt">Parser</span> <span class="kt">UTCTime</span>
<span class="nf">parseUTC</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">s</span> <span class="ow"><-</span> <span class="n">manyTill</span> <span class="n">anySingle</span> <span class="n">eol</span>
<span class="n">parseTimeM</span> <span class="kt">False</span> <span class="n">defaultTimeLocale</span> <span class="n">dateFormat</span> <span class="n">s</span></code></pre>
<ul>
<li>
<code>manyTill anySingle eol</code> parse jusqu'à la fin de ligne tous les caractères qui seront ensuite passés à <code>parseTimeM</code>.</li>
<li>
<code>parseTimeM ...</code> va parser la chaîne de caractère en fonction de la chaîne de format spécifiée par <code>dateFormat</code>. On voit ici une des forces de Haskell qui est la possibilité de combiner les fonctions de <code>Data.Time</code> et de <code>Text.Megaparsec</code> ensemble.</li>
</ul>
<h2 id="toc-conclusion">Conclusion</h2>
<p>GHC 8.6.1 n’apporte pas forcement des nouveautés capables de motiver à l’utilisation de Haskell, mais apporte son lot de petites améliorations qui, ensemble, rendent GHC plus pratique.</p>
<p>GHC 8.8, <a href="https://ghc.haskell.org/trac/ghc/wiki/Status/GHC-8.8.1">prévu pour mars 2019</a> devrait apporter son lot de nouveautés avancées au niveau du système de type, et fait quelques promesses concernant les performances du compilateur et du code généré.</p>
</div><div><a href="https://linuxfr.org/news/ghc-8-4-et-8-6.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/114819/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/ghc-8-4-et-8-6#comments">ouvrir dans le navigateur</a>
</p>
GuillaumgipoissonSnarkbubar🦥palm123Davy DefaudAnthony JaguenaudkarchnuBenoît SibaudBruno Micheloctachronhttps://linuxfr.org/nodes/114819/comments.atomtag:linuxfr.org,2005:Bookmark/2452018-08-26T15:19:19+02:002018-08-26T15:19:19+02:00Un serveur de webcam en 35 lignes de Haskell<a href="https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_25/2018-08-25-README.html">https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_25/2018-08-25-README.html</a> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/115142/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/liens/un-serveur-de-webcam-en-35-lignes-de-haskell#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/115142/comments.atomtag:linuxfr.org,2005:News/387482018-08-19T21:42:01+02:002018-08-20T10:19:09+02:00Développement Web frontend en Haskell, Elm et PurescriptLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Actuellement, le développement Web côté client (<em>frontend</em>) est très souvent réalisé en JavaScript ou dans des langages dérivés comme TypeScript. Il existe cependant d’autres outils intéressants, basés sur des langages de programmation fonctionnelle, qui permettent notamment d’éviter de nombreuses erreurs lors de l’exécution sur le navigateur.</p>
<p>L’objectif de cette dépêche est de rappeler quelques généralités sur le développement Web <em>frontend</em>, et de présenter les outils <a href="https://fr.wikipedia.org/wiki/Orme">Elm</a>, <a href="http://www.purescript.org/">Purescript</a>, Miso et Reflex, à partir d’un exemple d’application (galerie d’images fournie via une API Web).</p>
<p><em>Attention : ceci n’est pas une étude rigoureuse et avancée mais juste un petit retour de petite expérience.</em></p>
<p>Voir également le <a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_24/animals">dépôt de code de l’exemple</a> et une <a href="https://youtu.be/RtxxVQuaFq8">présentation en vidéo</a>.</p>
</div><ul></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-g%C3%A9n%C3%A9ralit%C3%A9s-sur-le-web-frontend">Généralités sur le web frontend</a><ul>
<li><a href="#toc-page-web-application-web-application-native">Page web, application web, application native</a></li>
<li><a href="#toc-les-langages-pour-le-web-frontend">Les langages pour le web frontend</a></li>
<li><a href="#toc-les-langages-fonctionnels-pour-le-web-frontend">Les langages fonctionnels pour le web frontend</a></li>
<li><a href="#toc-les-concepts-de-mvc-frp-virtual-dom">Les concepts de MVC, FRP, Virtual-DOM</a></li>
</ul>
</li>
<li><a href="#toc-un-exemple-de-base">Un exemple de base</a></li>
<li><a href="#toc-elm">Elm</a></li>
<li><a href="#toc-purescript">Purescript</a></li>
<li><a href="#toc-haskellmiso">Haskell/Miso</a></li>
<li><a href="#toc-haskellreflex">Haskell/Reflex</a></li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<h2 id="toc-généralités-sur-le-web-frontend">Généralités sur le web frontend</h2>
<h3 id="toc-page-web-application-web-application-native">Page web, application web, application native</h3>
<p>Historiquement, les pages web se contentaient d'afficher un contenu statique et de proposer des liens vers d'autres pages. Des éléments dynamiques sont ensuite progressivement apparus : animations, formulaires avec vérifications de saisies… si bien, qu'aujourd'hui, de nombreuses pages web sont de véritables interfaces utilisateurs, comparables aux logiciels classiques installés sur le système. On appelle ce genre de pages des <em>applications web</em> (exécutées par un navigateur web), à distinguer des <em>applications natives</em> (exécutées par le système d'exploitation). </p>
<p>D'un point de vue utilisateur, les applications web et les applitions natives sont de plus en plus proches. Par exemple, on trouve des applition web de traitement de texte (frama-pad, google-docs) qui possèdent une bonne part des fonctionnalités de leurs équivalents natifs (libreoffice, msoffice). </p>
<p>D'un point de vue développeur, les technologies utilisées sont historiquement très différentes. Les applications natives utilisent généralement des langages comme Java, C#, Swift et leurs frameworks associés. Les applications web utilisent les technologies issues du web HTML/CSS/JavaScript et dépendent quasi-systématiquement d'un accès réseau via des websockets, requêtes AJAX ou autres. Cependant, on note une convergence web/natif également à ce niveau, notamment avec l'apparition du framework Electron, qui permet d'utiliser des technologies web pour développer des applications natives. De même, des architectures logicielles comme le Modèle-Vue-Contrôleur, très courant en natif, a été repris dans de nombreux frameworks web.</p>
<h3 id="toc-les-langages-pour-le-web-frontend">Les langages pour le web frontend</h3>
<p>Le langage des applis web est sans conteste le JavaScript. C'est un langage assez controversé mais qui a tout de même des avantages indéniables, surtout depuis les récentes normes (ES2015…) : flexibilité, expressivité, compilateurs Just-In-Time performants, intégration dans les navigateurs…</p>
<p>Cependant, JavaScript permet facilement de faire des erreurs qui se produiront à l'exécution finale de l'application, les fameuses <em>runtime errors</em>. Pour éviter ces erreurs, on utilise souvent des outils comme des analyseurs statiques, debuggers ou tests unitaires. Une autre solution consiste à utiliser des frameworks (Angular, React…), des bibliothèques (Immutable, Redux…) voire des langages dérivés (TypeScript…) qui réduisent les sources d'erreurs possibles.</p>
<h3 id="toc-les-langages-fonctionnels-pour-le-web-frontend">Les langages fonctionnels pour le web frontend</h3>
<p>En fait, une bonne partie des solutions proposées pour rendre le développement en JavaScript plus robuste existe déjà naturellement dans des langages fonctionnels comme Haskell : immuabilité, fonctions pures, typage statique fort… Certains développeurs se sont donc naturellement inspirés des langages fonctionnels pour proposer des technos web frontend garantissant l'absence d'erreurs au runtime.</p>
<p>L'idée de ces technos est de fournir un écosystème complet (langage fonctionnel, compilateur, bibliothèques…) adapté au web et produisant du code JavaScript exécutable par un navigateur. Parmi ces technos, on trouve Elm et Purescript, qui proposent des langages inspirés d'Haskell. Il est également possible d'utiliser directement le langage Haskell (éventuellement avec des bibliothèques comme Miso ou Reflex) et le compilateur Ghcjs pour produire du code JavaScript. Enfin, il existe des outils dans d'autres langages fonctionnels comme ClojureScript, Ocsigen (OCaml)…</p>
<h3 id="toc-les-concepts-de-mvc-frp-virtual-dom">Les concepts de MVC, FRP, Virtual-DOM</h3>
<p>Ces trois concepts sont généralement au cœur des technos web frontend fonctionnelles. Ils sont également assez fréquents dans l'écosystème JavaScript classique.</p>
<p>Le MVC (Model-View-Controler) est une architecture de code qui permet d'organiser une application en trois parties : le modèle (données « métier » à manipuler), la vue (affichage présenté à l'utilisateur) et le contrôleur (mise à jour de l'application en fonction des événements). Généralement, un MVC gére les événements de façon asynchrone et unidirectionnelle : les événements de la vue sont passés au contrôleur, qui modifie le modèle puis lance un rafraichissement de la vue…</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f6d76632e6a7067/mvc.jpg" alt="MVC" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images/mvc.jpg"></p>
<p><a href="https://www.futurice.com/blog/elm-in-the-real-world/">crédit : Ossi Hanhinen</a></p>
<p>Le FRP (Functional Reactive Programming) est le principe de base des frameworks fonctionnels, sur lequel est implémenté le MVC. Le FRP permet d'implémenter le comportement dynamique des interfaces utilisateur. Il traite les flux d'événements au cours du temps et transmet ces flux entre les différents composants de l'application, le tout de façon fonctionnelle (sans effet de bord).</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f6672702e6a7067/frp.jpg" alt="Functional Reactive Programming" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images/frp.jpg"></p>
<p><a href="https://gist.github.com/staltz/868e7e9bc2a7b8c1f754">crédit : André Staltz</a></p>
<p>Enfin, un concept essentiel aux applications web est le DOM virtuel. Le DOM (Document-Object-Model) décrit la structure d'une page web, donc de l'application, dans le navigateur. Au cours de son exécution, une appli web a besoin de manipuler le DOM, pour récupérer des données ou pour modifier l'affichage. Or, manipuler directement le DOM est coûteux et résulte en une application peu réactive. Pour améliorer les performances, les frameworks web utilisent un système de cache, le DOM virtuel, qui regroupe des modifications et ainsi minimise les accès au DOM.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f2f76646f6d2e6a7067/vdom.jpg" alt="DOM et DOM virtuel" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images//vdom.jpg"></p>
<p><a href="https://medium.com/naukri-engineering/naukriengineering-virtual-dom-fa8019c626b">crédit : Naukri Engineering</a></p>
<h2 id="toc-un-exemple-de-base">Un exemple de base</h2>
<p>Pour illustrer les outils de web frontend fonctionnel, imaginons qu'on veuille implémenter une appli client-serveur de recherche et d'affichage d'images d'animaux. Le serveur fournit les images en HTTP à l'URL <code>/img/<nom-du-fichier></code>. Il fournit également une API JSON à l'URL <code>/api/animals <prefix></code> auquel il répond par la liste des animaux de sa base de données dont le type correspond au préfixe donné en paramètre. Chaque élément de la liste retournée contient le type de l'animal et le nom du fichier image (accessible via l'URL <code>/img</code>). Si aucun préfixe n'est donné, le serveur retourne la liste complète.</p>
<p>L'appli web client à réaliser contient simplement une zone de texte permettant de saisir le préfixe. Il envoie des requêtes AJAX au serveur pour récupérer les animaux correspondant puis affiche les images correspondantes après les avoir également demandées au serveur.</p>
<p>Ci-dessous une image du client implémenté en Purescript. L'ensemble du code est disponible sur ce <a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_24/animals">dépôt git</a>.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f2f7475746f32342d616e696d616c732e6a7067/tuto24-animals.jpg" alt="client en purescript" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images//tuto24-animals.jpg"></p>
<h2 id="toc-elm">Elm</h2>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f2f656c6d2e6a7067/elm.jpg" alt="logo ELM" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images//elm.jpg"></p>
<p><a href="http://elm-lang.org/">Elm</a> est un environnement complet de développement web frontend fonctionnel. Il fournit un langage fonctionnel (inspiré d'Haskell mais en beaucoup plus simple), un compilateur Elm vers JavaScript et des bibliothèques. Elm est basé sur un DOM virtuel performant et permet de développer des applis web selon une architecture MVC.</p>
<p>Ci-dessous une implémentation en Elm de l'application d'exemple (note pour les développeurs Elm : désolé pour le non-respect des règles de formatage de code Elm mais j'ai subi des pressions de la part de la ligue de protection des molettes de souris). Ce code suit un schéma MVC et un style fonctionnel très classique. On définit un type <code>Animal</code>, avec ses fonctions de décodage de données JSON, ainsi que le modèle de l'application, c'est-à-dire simplement la liste des <code>Animal</code> à afficher.</p>
<p>Au niveau du contrôleur, le type <code>Msg</code> définit les événements qui peuvent se produire. L'événement <code>MsgInput</code> modélise une action de l'utilisateur sur la zone de texte et l'événement <code>MsgAnimals</code> un message du serveur transmettant les <code>Animal</code> en réponse à une requête à l'API. Ces événements sont gérés dans la fonction <code>update</code> : <code>MsgInput</code> ne change pas le modèle mais lance une requête à l'API via la fonction <code>queryAnimals</code>, <code>MsgAnimals</code> met à jour le modèle avec les données <code>Animal</code> reçues. </p>
<p>Enfin, la fonction <code>view</code> indique comment construire la vue de l'appli, à partir du modèle : un titre <code>h1</code>, une zone de texte <code>input</code> puis un <code>div</code> pour chaque <code>Animal</code> du modèle. </p>
<pre><code class="haskell"><span class="kr">module</span> <span class="nn">Main</span> <span class="n">exposing</span> <span class="p">(</span><span class="o">..</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Html</span> <span class="n">exposing</span> <span class="p">(</span><span class="o">..</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Html.Attributes</span> <span class="n">exposing</span> <span class="p">(</span><span class="n">height</span><span class="p">,</span> <span class="n">href</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">width</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Html.Events</span> <span class="n">exposing</span> <span class="p">(</span><span class="n">onClick</span><span class="p">,</span> <span class="n">onInput</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Http</span>
<span class="kr">import</span> <span class="nn">Json.Decode</span> <span class="k">as</span> <span class="n">JD</span>
<span class="nf">main</span> <span class="kt">:</span> <span class="kt">Program</span> <span class="kt">Never</span> <span class="kt">Model</span> <span class="kt">Msg</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kt">Html</span><span class="o">.</span><span class="n">program</span>
<span class="p">{</span> <span class="n">init</span> <span class="ow">=</span> <span class="n">init</span>
<span class="p">,</span> <span class="n">view</span> <span class="ow">=</span> <span class="n">view</span>
<span class="p">,</span> <span class="n">update</span> <span class="ow">=</span> <span class="n">update</span>
<span class="p">,</span> <span class="n">subscriptions</span> <span class="ow">=</span> <span class="n">subscriptions</span>
<span class="p">}</span>
<span class="c1">-- Model</span>
<span class="kr">type</span> <span class="n">alias</span> <span class="kt">Animal</span> <span class="ow">=</span>
<span class="p">{</span> <span class="n">animalType</span> <span class="kt">:</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">animalImage</span> <span class="kt">:</span> <span class="kt">String</span>
<span class="p">}</span>
<span class="nf">decodeAnimal</span> <span class="kt">:</span> <span class="kt">JD</span><span class="o">.</span><span class="kt">Decoder</span> <span class="kt">Animal</span>
<span class="nf">decodeAnimal</span> <span class="ow">=</span> <span class="kt">JD</span><span class="o">.</span><span class="n">map2</span> <span class="kt">Animal</span>
<span class="p">(</span><span class="kt">JD</span><span class="o">.</span><span class="n">field</span> <span class="s">"animalType"</span> <span class="kt">JD</span><span class="o">.</span><span class="n">string</span><span class="p">)</span>
<span class="p">(</span><span class="kt">JD</span><span class="o">.</span><span class="n">field</span> <span class="s">"animalImage"</span> <span class="kt">JD</span><span class="o">.</span><span class="n">string</span><span class="p">)</span>
<span class="nf">decodeAnimalList</span> <span class="kt">:</span> <span class="kt">JD</span><span class="o">.</span><span class="kt">Decoder</span> <span class="p">(</span><span class="kt">List</span> <span class="kt">Animal</span><span class="p">)</span>
<span class="nf">decodeAnimalList</span> <span class="ow">=</span> <span class="kt">JD</span><span class="o">.</span><span class="n">list</span> <span class="n">decodeAnimal</span>
<span class="kr">type</span> <span class="n">alias</span> <span class="kt">Model</span> <span class="ow">=</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="kt">:</span> <span class="kt">List</span> <span class="kt">Animal</span> <span class="p">}</span>
<span class="nf">init</span> <span class="kt">:</span> <span class="p">(</span> <span class="kt">Model</span><span class="p">,</span> <span class="kt">Cmd</span> <span class="kt">Msg</span> <span class="p">)</span>
<span class="nf">init</span> <span class="ow">=</span> <span class="p">(</span> <span class="kt">Model</span> <span class="kt">[]</span><span class="p">,</span> <span class="n">queryAnimals</span> <span class="s">""</span> <span class="p">)</span>
<span class="c1">-- Controler</span>
<span class="nf">subscriptions</span> <span class="kt">:</span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">Sub</span> <span class="kt">Msg</span>
<span class="nf">subscriptions</span> <span class="kr">_</span> <span class="ow">=</span> <span class="kt">Sub</span><span class="o">.</span><span class="n">none</span>
<span class="kr">type</span> <span class="kt">Msg</span>
<span class="ow">=</span> <span class="kt">MsgInput</span> <span class="kt">String</span>
<span class="o">|</span> <span class="kt">MsgAnimals</span> <span class="p">(</span><span class="kt">Result</span> <span class="kt">Http</span><span class="o">.</span><span class="kt">Error</span> <span class="p">(</span><span class="kt">List</span> <span class="kt">Animal</span><span class="p">))</span>
<span class="nf">update</span> <span class="kt">:</span> <span class="kt">Msg</span> <span class="ow">-></span> <span class="kt">Model</span> <span class="ow">-></span> <span class="p">(</span> <span class="kt">Model</span><span class="p">,</span> <span class="kt">Cmd</span> <span class="kt">Msg</span> <span class="p">)</span>
<span class="nf">update</span> <span class="n">msg</span> <span class="n">model</span> <span class="ow">=</span>
<span class="kr">case</span> <span class="n">msg</span> <span class="kr">of</span>
<span class="kt">MsgInput</span> <span class="n">animalType</span> <span class="ow">-></span> <span class="p">(</span> <span class="n">model</span><span class="p">,</span> <span class="n">queryAnimals</span> <span class="n">animalType</span> <span class="p">)</span>
<span class="kt">MsgAnimals</span> <span class="p">(</span><span class="kt">Ok</span> <span class="kt">Model</span> <span class="n">animals</span><span class="p">)</span> <span class="ow">-></span> <span class="p">(</span> <span class="kt">Model</span> <span class="n">animals</span><span class="p">,</span> <span class="kt">Cmd</span><span class="o">.</span><span class="n">none</span> <span class="p">)</span>
<span class="kt">MsgAnimals</span> <span class="p">(</span><span class="kt">Err</span> <span class="kr">_</span><span class="p">)</span> <span class="ow">-></span> <span class="p">(</span> <span class="kt">Model</span> <span class="kt">[]</span><span class="p">,</span> <span class="kt">Cmd</span><span class="o">.</span><span class="n">none</span> <span class="p">)</span>
<span class="nf">queryAnimals</span> <span class="kt">:</span> <span class="kt">String</span> <span class="ow">-></span> <span class="kt">Cmd</span> <span class="kt">Msg</span>
<span class="nf">queryAnimals</span> <span class="n">txt</span> <span class="ow">=</span>
<span class="kr">let</span> <span class="n">url</span> <span class="ow">=</span> <span class="s">"http://localhost:3000/api/animals/"</span> <span class="o">++</span> <span class="n">txt</span>
<span class="kr">in</span> <span class="kt">Http</span><span class="o">.</span><span class="n">send</span> <span class="kt">MsgAnimals</span> <span class="p">(</span><span class="kt">Http</span><span class="o">.</span><span class="n">get</span> <span class="n">url</span> <span class="n">decodeAnimalList</span><span class="p">)</span>
<span class="c1">-- View</span>
<span class="nf">view</span> <span class="n">model</span> <span class="ow">=</span>
<span class="n">span</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">h1</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">text</span> <span class="s">"Animals (Elm)"</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">p</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">input</span> <span class="p">[</span> <span class="n">onInput</span> <span class="kt">MsgInput</span> <span class="p">]</span> <span class="kt">[]</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">span</span> <span class="kt">[]</span>
<span class="p">(</span><span class="kt">List</span><span class="o">.</span><span class="n">map</span>
<span class="p">(</span><span class="nf">\</span><span class="n">a</span> <span class="ow">-></span> <span class="n">div</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">p</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">text</span> <span class="n">a</span><span class="o">.</span><span class="n">animalType</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">img</span>
<span class="p">[</span> <span class="n">src</span> <span class="p">(</span><span class="s">"http://localhost:3000/img/"</span> <span class="o">++</span> <span class="n">a</span><span class="o">.</span><span class="n">animalImage</span><span class="p">)</span>
<span class="p">,</span> <span class="n">height</span> <span class="mi">240</span>
<span class="p">,</span> <span class="n">width</span> <span class="mi">320</span>
<span class="p">]</span>
<span class="kt">[]</span>
<span class="p">]</span>
<span class="p">)</span> <span class="n">model</span><span class="o">.</span><span class="n">modelAnimals</span>
<span class="p">)</span>
<span class="p">]</span></code></pre>
<p>Elm a l'avantage d'être particulièrement simple à utiliser. Les applis développées suivent toujours un MVC bien définis et les messages du compilateur sont particulièrement clairs. Le code JavaScript produit est léger et performant. Parmi les inconvénients, on peut noter que Elm est limité au schéma MVC qui, bien que très répandu, ne sera peut-être pas adapté à toutes les applications.</p>
<h2 id="toc-purescript">Purescript</h2>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f2f707572732e6a7067/purs.jpg" alt="logo purescript" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images//purs.jpg"></p>
<p><a href="http://www.purescript.org/">Purescript</a> est également un environnement complet (langage, compilateur, bibliothèques) mais plus général que Elm. En effet, son langage est plus proche d'Haskell et plus puissant (il supporte notamment les classes de type). De plus, Purescript propose différentes architecture de code, dont MVC. Enfin, Purescript peut être également utilisé pour le développement côté serveur.</p>
<p>Ci-dessous une implémentation en Purescript de l’application d’exemple, utilisant la bibliothèque Halogen (FRP + DOM virtuel). Ce code est très proche du code Elm. Pour le modèle, on définit également un type <code>Animals</code>, avec les fonctions de décodage JSON, et un type <code>Model</code>. Pour le contrôleur, on définit un type <code>Query</code> qui permet de gérer la requête AJAX et sa réponse, via la fonction <code>eval</code>. Enfin, la vue suit le même schéma que l'implémentation en Elm.</p>
<pre><code class="haskell"><span class="kr">module</span> <span class="nn">Main</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Aff</span> <span class="p">(</span><span class="kt">Aff</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Eff</span> <span class="p">(</span><span class="kt">Eff</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Argonaut</span> <span class="p">((</span><span class="o">.?</span><span class="p">),</span> <span class="nf">class</span> <span class="kt">DecodeJson</span><span class="p">,</span> <span class="nf">decodeJson</span><span class="p">,</span> <span class="kt">Json</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Either</span> <span class="p">(</span><span class="kt">Either</span><span class="p">(</span><span class="o">..</span><span class="p">))</span>
<span class="kr">import</span> <span class="nn">Data.Maybe</span> <span class="p">(</span><span class="kt">Maybe</span><span class="p">(</span><span class="o">..</span><span class="p">))</span>
<span class="kr">import</span> <span class="nn">Data.Traversable</span> <span class="p">(</span><span class="nf">traverse</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Halogen</span> <span class="k">as</span> <span class="n">H</span>
<span class="kr">import</span> <span class="nn">Halogen.Aff</span> <span class="k">as</span> <span class="n">HA</span>
<span class="kr">import</span> <span class="nn">Halogen.HTML</span> <span class="k">as</span> <span class="n">HH</span>
<span class="kr">import</span> <span class="nn">Halogen.HTML.Events</span> <span class="k">as</span> <span class="n">HE</span>
<span class="kr">import</span> <span class="nn">Halogen.HTML.Properties</span> <span class="k">as</span> <span class="n">HP</span>
<span class="kr">import</span> <span class="nn">Halogen.VDom.Driver</span> <span class="p">(</span><span class="nf">runUI</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Network.HTTP.Affjax</span> <span class="k">as</span> <span class="n">AX</span>
<span class="kr">import</span> <span class="nn">Prelude</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">Eff</span> <span class="p">(</span><span class="kt">HA</span><span class="o">.</span><span class="kt">HalogenEffects</span> <span class="p">(</span><span class="n">ajax</span> <span class="ow">::</span> <span class="kt">AX</span><span class="o">.</span><span class="kt">AJAX</span><span class="p">))</span> <span class="kt">Unit</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kt">HA</span><span class="o">.</span><span class="n">runHalogenAff</span> <span class="kr">do</span>
<span class="n">body</span> <span class="ow"><-</span> <span class="kt">HA</span><span class="o">.</span><span class="n">awaitBody</span>
<span class="n">io</span> <span class="ow"><-</span> <span class="n">runUI</span> <span class="n">ui</span> <span class="n">unit</span> <span class="n">body</span>
<span class="n">io</span><span class="o">.</span><span class="n">query</span> <span class="o">$</span> <span class="kt">H</span><span class="o">.</span><span class="n">action</span> <span class="o">$</span> <span class="kt">QueryAnimals</span> <span class="s">""</span>
<span class="nf">ui</span> <span class="ow">::</span> <span class="n">forall</span> <span class="n">eff</span><span class="o">.</span> <span class="kt">H</span><span class="o">.</span><span class="kt">Component</span> <span class="kt">HH</span><span class="o">.</span><span class="kt">HTML</span> <span class="kt">Query</span> <span class="kt">Unit</span> <span class="kt">Void</span> <span class="p">(</span><span class="kt">Aff</span> <span class="p">(</span><span class="n">ajax</span> <span class="ow">::</span> <span class="kt">AX</span><span class="o">.</span><span class="kt">AJAX</span> <span class="o">|</span> <span class="n">eff</span><span class="p">))</span>
<span class="nf">ui</span> <span class="ow">=</span> <span class="kt">H</span><span class="o">.</span><span class="n">component</span>
<span class="p">{</span> <span class="n">initialState</span><span class="kt">:</span> <span class="n">const</span> <span class="n">initialState</span>
<span class="p">,</span> <span class="n">render</span>
<span class="p">,</span> <span class="n">eval</span>
<span class="p">,</span> <span class="n">receiver</span><span class="kt">:</span> <span class="n">const</span> <span class="kt">Nothing</span>
<span class="p">}</span>
<span class="c1">-- Model</span>
<span class="kr">newtype</span> <span class="kt">Animal</span> <span class="ow">=</span> <span class="kt">Animal</span>
<span class="p">{</span> <span class="n">animalType</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">,</span> <span class="n">animalImage</span> <span class="ow">::</span> <span class="kt">String</span>
<span class="p">}</span>
<span class="kr">instance</span> <span class="n">decodeJsonBlogPost</span> <span class="ow">::</span> <span class="kt">DecodeJson</span> <span class="kt">Animal</span> <span class="kr">where</span>
<span class="n">decodeJson</span> <span class="n">json</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">obj</span> <span class="ow"><-</span> <span class="n">decodeJson</span> <span class="n">json</span>
<span class="n">animalType</span> <span class="ow"><-</span> <span class="n">obj</span> <span class="o">.?</span> <span class="s">"animalType"</span>
<span class="n">animalImage</span> <span class="ow"><-</span> <span class="n">obj</span> <span class="o">.?</span> <span class="s">"animalImage"</span>
<span class="n">pure</span> <span class="o">$</span> <span class="kt">Animal</span> <span class="p">{</span> <span class="n">animalType</span><span class="p">,</span> <span class="n">animalImage</span> <span class="p">}</span>
<span class="nf">decodeAnimalArray</span> <span class="ow">::</span> <span class="kt">Json</span> <span class="ow">-></span> <span class="kt">Either</span> <span class="kt">String</span> <span class="p">(</span><span class="kt">Array</span> <span class="kt">Animal</span><span class="p">)</span>
<span class="nf">decodeAnimalArray</span> <span class="n">json</span> <span class="ow">=</span> <span class="n">decodeJson</span> <span class="n">json</span> <span class="o">>>=</span> <span class="n">traverse</span> <span class="n">decodeJson</span>
<span class="kr">type</span> <span class="kt">Model</span> <span class="ow">=</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="ow">::</span> <span class="kt">Array</span> <span class="kt">Animal</span> <span class="p">}</span>
<span class="nf">initialState</span> <span class="ow">::</span> <span class="kt">Model</span>
<span class="nf">initialState</span> <span class="ow">=</span> <span class="p">{</span> <span class="n">modelAnimals</span><span class="kt">:</span> <span class="kt">[]</span> <span class="p">}</span>
<span class="c1">-- Controler</span>
<span class="kr">data</span> <span class="kt">Query</span> <span class="n">a</span> <span class="ow">=</span> <span class="kt">QueryAnimals</span> <span class="kt">String</span> <span class="n">a</span>
<span class="nf">eval</span> <span class="ow">::</span> <span class="n">forall</span> <span class="n">eff</span><span class="o">.</span> <span class="kt">Query</span> <span class="o">~></span> <span class="kt">H</span><span class="o">.</span><span class="kt">ComponentDSL</span> <span class="kt">Model</span> <span class="kt">Query</span> <span class="kt">Void</span> <span class="p">(</span><span class="kt">Aff</span> <span class="p">(</span><span class="n">ajax</span> <span class="ow">::</span> <span class="kt">AX</span><span class="o">.</span><span class="kt">AJAX</span> <span class="o">|</span> <span class="n">eff</span><span class="p">))</span>
<span class="nf">eval</span> <span class="p">(</span><span class="kt">QueryAnimals</span> <span class="n">animal_type</span> <span class="n">next</span><span class="p">)</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kt">H</span><span class="o">.</span><span class="n">modify</span> <span class="p">(</span><span class="kr">_</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="ow">=</span> <span class="kt">[]</span> <span class="p">})</span>
<span class="n">response</span> <span class="ow"><-</span> <span class="kt">H</span><span class="o">.</span><span class="n">liftAff</span> <span class="o">$</span> <span class="kt">AX</span><span class="o">.</span><span class="n">get</span> <span class="p">(</span><span class="s">"http://localhost:3000/api/animals/"</span> <span class="o"><></span> <span class="n">animal_type</span><span class="p">)</span>
<span class="kr">let</span> <span class="n">animals</span> <span class="ow">=</span> <span class="kr">case</span> <span class="n">decodeAnimalArray</span> <span class="n">response</span><span class="o">.</span><span class="n">response</span> <span class="kr">of</span>
<span class="kt">Left</span> <span class="kr">_</span> <span class="ow">-></span> <span class="kt">[]</span>
<span class="kt">Right</span> <span class="n">ra</span> <span class="ow">-></span> <span class="n">ra</span>
<span class="kt">H</span><span class="o">.</span><span class="n">modify</span> <span class="p">(</span><span class="kr">_</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="ow">=</span> <span class="n">animals</span> <span class="p">})</span>
<span class="n">pure</span> <span class="n">next</span>
<span class="c1">-- View</span>
<span class="nf">render</span> <span class="ow">::</span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">H</span><span class="o">.</span><span class="kt">ComponentHTML</span> <span class="kt">Query</span>
<span class="nf">render</span> <span class="n">m</span> <span class="ow">=</span>
<span class="kt">HH</span><span class="o">.</span><span class="n">span</span> <span class="kt">[]</span>
<span class="p">[</span> <span class="kt">HH</span><span class="o">.</span><span class="n">h1</span> <span class="kt">[]</span> <span class="p">[</span> <span class="kt">HH</span><span class="o">.</span><span class="n">text</span> <span class="s">"Animals (Purescript)"</span> <span class="p">]</span>
<span class="p">,</span> <span class="kt">HH</span><span class="o">.</span><span class="n">p</span> <span class="kt">[]</span> <span class="p">[</span> <span class="kt">HH</span><span class="o">.</span><span class="n">input</span> <span class="p">[</span> <span class="kt">HE</span><span class="o">.</span><span class="n">onValueInput</span> <span class="p">(</span><span class="kt">HE</span><span class="o">.</span><span class="n">input</span> <span class="kt">QueryAnimals</span><span class="p">)</span> <span class="p">]</span> <span class="p">]</span>
<span class="p">,</span> <span class="kt">HH</span><span class="o">.</span><span class="n">span</span> <span class="kt">[]</span>
<span class="p">(</span><span class="n">map</span> <span class="p">(</span><span class="nf">\</span> <span class="p">(</span><span class="kt">Animal</span> <span class="p">{</span><span class="n">animalType</span><span class="p">,</span> <span class="n">animalImage</span><span class="p">})</span>
<span class="ow">-></span> <span class="kt">HH</span><span class="o">.</span><span class="n">div</span> <span class="kt">[]</span>
<span class="p">[</span> <span class="kt">HH</span><span class="o">.</span><span class="n">p</span> <span class="kt">[]</span> <span class="p">[</span> <span class="kt">HH</span><span class="o">.</span><span class="n">text</span> <span class="n">animalType</span> <span class="p">]</span>
<span class="p">,</span> <span class="kt">HH</span><span class="o">.</span><span class="n">img</span> <span class="p">[</span> <span class="kt">HP</span><span class="o">.</span><span class="n">src</span> <span class="p">(</span><span class="s">"http://localhost:3000/img/"</span> <span class="o"><></span> <span class="n">animalImage</span><span class="p">)</span>
<span class="p">,</span> <span class="kt">HP</span><span class="o">.</span><span class="n">width</span> <span class="mi">320</span>
<span class="p">,</span> <span class="kt">HP</span><span class="o">.</span><span class="n">height</span> <span class="mi">240</span>
<span class="p">]</span>
<span class="p">]</span>
<span class="p">)</span> <span class="n">m</span><span class="o">.</span><span class="n">modelAnimals</span><span class="p">)</span>
<span class="p">]</span></code></pre>
<p>Purescript a l'avantage d'être plus puissant et plus général que Elm. En contrepartie, il est moins simple à utiliser. Son environnement est également plus compliqué à utiliser : il faut gérer les dépendances Purescript avec <code>bower</code>, les dépendances nodejs avec <code>npm</code> et la compilation avec <code>pulp</code>. </p>
<h2 id="toc-haskellmiso">Haskell/Miso</h2>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f6d69736f2e6a7067/miso.jpg" alt="Miso" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images/miso.jpg"></p>
<p><a href="https://haskell-miso.org">Miso</a> est une bibliothèque Haskell pour développer des applis web frontend. Miso permet de coder une appli MVC + DOM virtuel et de la compiler en JavaScript grâce à Ghcjs.</p>
<p>Ci-dessous une implémentation en Haskell + Miso de l’application d’exemple. Ce code est très similaire à l'implémentation en Elm. Miso annonce d'ailleurs explicitement s'inspirer de Elm.</p>
<pre><code class="haskell"><span class="cm">{-# LANGUAGE DeriveGeneric #-}</span>
<span class="cm">{-# LANGUAGE OverloadedStrings #-}</span>
<span class="kr">import</span> <span class="nn">Data.Aeson</span> <span class="p">(</span><span class="kt">FromJSON</span><span class="p">,</span> <span class="nf">decodeStrict</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Maybe</span> <span class="p">(</span><span class="nf">fromMaybe</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Monoid</span> <span class="p">((</span><span class="o"><></span><span class="p">))</span>
<span class="kr">import</span> <span class="nn">Data.Text</span> <span class="p">(</span><span class="kt">Text</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">GHC.Generics</span> <span class="p">(</span><span class="kt">Generic</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">JavaScript.Web.XMLHttpRequest</span> <span class="p">(</span><span class="kt">Request</span><span class="p">(</span><span class="o">..</span><span class="p">),</span> <span class="kt">RequestData</span><span class="p">(</span><span class="o">..</span><span class="p">),</span> <span class="kt">Method</span><span class="p">(</span><span class="o">..</span><span class="p">),</span> <span class="nf">contents</span><span class="p">,</span> <span class="nf">xhrByteString</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Miso</span>
<span class="kr">import</span> <span class="nn">Miso.String</span> <span class="p">(</span><span class="kt">MisoString</span><span class="p">,</span> <span class="nf">toMisoString</span><span class="p">,</span> <span class="nf">fromMisoString</span><span class="p">,</span> <span class="nf">pack</span><span class="p">)</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="n">startApp</span> <span class="kt">App</span>
<span class="p">{</span> <span class="n">model</span> <span class="ow">=</span> <span class="kt">Model</span> <span class="kt">[]</span>
<span class="p">,</span> <span class="n">update</span> <span class="ow">=</span> <span class="n">updateModel</span>
<span class="p">,</span> <span class="n">view</span> <span class="ow">=</span> <span class="n">viewModel</span>
<span class="p">,</span> <span class="n">subs</span> <span class="ow">=</span> <span class="kt">[]</span>
<span class="p">,</span> <span class="n">events</span> <span class="ow">=</span> <span class="n">defaultEvents</span>
<span class="p">,</span> <span class="n">initialAction</span> <span class="ow">=</span> <span class="kt">GetAnimals</span> <span class="s">""</span>
<span class="p">,</span> <span class="n">mountPoint</span> <span class="ow">=</span> <span class="kt">Nothing</span>
<span class="p">}</span>
<span class="c1">-- Model</span>
<span class="kr">data</span> <span class="kt">Animal</span> <span class="ow">=</span> <span class="kt">Animal</span>
<span class="p">{</span> <span class="n">animalType</span> <span class="ow">::</span> <span class="kt">Text</span>
<span class="p">,</span> <span class="n">animalImage</span> <span class="ow">::</span> <span class="kt">Text</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Generic</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">FromJSON</span> <span class="kt">Animal</span>
<span class="kr">data</span> <span class="kt">Model</span> <span class="ow">=</span> <span class="kt">Model</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Animal</span><span class="p">]</span> <span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span>
<span class="c1">-- Controler</span>
<span class="kr">data</span> <span class="kt">Action</span>
<span class="ow">=</span> <span class="kt">GetAnimals</span> <span class="kt">MisoString</span>
<span class="o">|</span> <span class="kt">SetAnimals</span> <span class="p">[</span><span class="kt">Animal</span><span class="p">]</span>
<span class="o">|</span> <span class="kt">NoOp</span>
<span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Eq</span><span class="p">)</span>
<span class="nf">updateModel</span> <span class="ow">::</span> <span class="kt">Action</span> <span class="ow">-></span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">Effect</span> <span class="kt">Action</span> <span class="kt">Model</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">GetAnimals</span> <span class="n">str</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="o"><#</span> <span class="p">(</span><span class="kt">SetAnimals</span> <span class="o"><$></span> <span class="n">queryAnimals</span> <span class="n">str</span><span class="p">)</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">SetAnimals</span> <span class="n">animals</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">noEff</span> <span class="n">m</span> <span class="p">{</span> <span class="n">modelAnimals</span> <span class="ow">=</span> <span class="n">animals</span> <span class="p">}</span>
<span class="nf">updateModel</span> <span class="kt">NoOp</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">noEff</span> <span class="n">m</span>
<span class="nf">queryAnimals</span> <span class="ow">::</span> <span class="kt">MisoString</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="p">[</span><span class="kt">Animal</span><span class="p">]</span>
<span class="nf">queryAnimals</span> <span class="n">str</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">uri</span> <span class="ow">=</span> <span class="n">pack</span> <span class="o">$</span> <span class="s">"http://localhost:3000/api/animals/"</span> <span class="o">++</span> <span class="n">fromMisoString</span> <span class="n">str</span>
<span class="n">req</span> <span class="ow">=</span> <span class="kt">Request</span> <span class="kt">GET</span> <span class="n">uri</span> <span class="kt">Nothing</span> <span class="kt">[]</span> <span class="kt">False</span> <span class="kt">NoData</span>
<span class="kt">Just</span> <span class="n">cont</span> <span class="ow"><-</span> <span class="n">contents</span> <span class="o"><$></span> <span class="n">xhrByteString</span> <span class="n">req</span>
<span class="n">return</span> <span class="o">$</span> <span class="n">fromMaybe</span> <span class="kt">[]</span> <span class="o">$</span> <span class="n">decodeStrict</span> <span class="n">cont</span>
<span class="c1">-- View</span>
<span class="nf">viewModel</span> <span class="ow">::</span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">View</span> <span class="kt">Action</span>
<span class="nf">viewModel</span> <span class="p">(</span><span class="kt">Model</span> <span class="n">animals</span><span class="p">)</span> <span class="ow">=</span>
<span class="n">span_</span> <span class="kt">[]</span>
<span class="p">[</span> <span class="n">h1_</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">text</span> <span class="s">"Animals (Miso)"</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">p_</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">input_</span> <span class="p">[</span> <span class="n">onInput</span> <span class="kt">GetAnimals</span> <span class="p">]</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">span_</span> <span class="kt">[]</span> <span class="o">$</span> <span class="n">map</span> <span class="n">fmtAnimal</span> <span class="n">animals</span>
<span class="p">]</span>
<span class="nf">fmtAnimal</span> <span class="ow">::</span> <span class="kt">Animal</span> <span class="ow">-></span> <span class="kt">View</span> <span class="kt">Action</span>
<span class="nf">fmtAnimal</span> <span class="n">animal</span> <span class="ow">=</span>
<span class="n">div_</span> <span class="kt">[]</span>
<span class="p">[</span> <span class="n">p_</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">text</span> <span class="o">$</span> <span class="n">toMisoString</span> <span class="o">$</span> <span class="n">animalType</span> <span class="n">animal</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">img_</span> <span class="p">[</span> <span class="n">src_</span> <span class="o">$</span> <span class="n">toMisoString</span> <span class="o">$</span> <span class="s">"http://localhost:3000/img/"</span> <span class="o"><></span> <span class="n">animalImage</span> <span class="n">animal</span>
<span class="p">,</span> <span class="n">width_</span> <span class="s">"320"</span>
<span class="p">,</span> <span class="n">height_</span> <span class="s">"240"</span>
<span class="p">]</span>
<span class="p">]</span></code></pre>
<p>Pour un développeur Haskell, Miso est une bibliothèque intéressante, car elle permet d'implémenter des applis simples « à la Elm » tout en restant dans l'écosystème Haskell. En revanche, son environnement est moins mature : les outils Ghcjs + Miso + Nix ne sont pas complètement triviaux à mettre en place et les temps d'installation d'installation et de compilation plus longs.</p>
<h2 id="toc-haskellreflex">Haskell/Reflex</h2>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f32342f696d616765732f2f7265666c65782e6a7067/reflex.jpg" alt="logo Reflex" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_24/images//reflex.jpg"></p>
<p><a href="https://reflex-frp.org/">Reflex</a> est une bibliothèque Haskell de FRP générique. Elle est complétée par des projets associés : reflex-dom (DOM virtuel), reflex-platform (système de compilation multi-plateforme)… Une application Reflex peut être compilée avec Ghc ou avec Ghcjs et ainsi produire des applications web ou natives (PC ou mobile).</p>
<p>Ci-dessous une implémentation en Haskell + Reflex de l’application d’exemple. Contrairement aux implémentations précédentes, ce code ne suit pas une architecture MVC mais gère explicitement les éléments graphiques et leurs flux d'événements. Il est tout à fait possible d'organiser le code selon un MVC mais ceci est à la charge du programmeur.</p>
<pre><code class="haskell"><span class="cm">{-# LANGUAGE DeriveGeneric #-}</span>
<span class="cm">{-# LANGUAGE OverloadedStrings #-}</span>
<span class="cm">{-# LANGUAGE ScopedTypeVariables #-}</span>
<span class="kr">import</span> <span class="nn">Data.Aeson</span> <span class="p">(</span><span class="kt">FromJSON</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Default</span> <span class="p">(</span><span class="nf">def</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Maybe</span> <span class="p">(</span><span class="nf">fromJust</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Data.Monoid</span> <span class="p">((</span><span class="o"><></span><span class="p">))</span>
<span class="kr">import</span> <span class="nn">Data.Text</span> <span class="p">(</span><span class="kt">Text</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">GHC.Generics</span> <span class="p">(</span><span class="kt">Generic</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Prelude</span>
<span class="kr">import</span> <span class="nn">Reflex</span> <span class="p">(</span><span class="nf">holdDyn</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Reflex.Dom</span>
<span class="kr">import</span> <span class="nn">Reflex.Dom.Xhr</span> <span class="p">(</span><span class="nf">decodeXhrResponse</span><span class="p">,</span> <span class="nf">performRequestAsync</span><span class="p">,</span> <span class="kt">XhrRequest</span><span class="p">(</span><span class="o">..</span><span class="p">))</span>
<span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="n">mainWidget</span> <span class="n">ui</span>
<span class="c1">-- Model</span>
<span class="kr">data</span> <span class="kt">Animal</span> <span class="ow">=</span> <span class="kt">Animal</span>
<span class="p">{</span> <span class="n">animalType</span> <span class="ow">::</span> <span class="kt">Text</span>
<span class="p">,</span> <span class="n">animalImage</span> <span class="ow">::</span> <span class="kt">Text</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Generic</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">FromJSON</span> <span class="kt">Animal</span>
<span class="c1">-- View / Controler</span>
<span class="nf">ui</span> <span class="ow">::</span> <span class="kt">MonadWidget</span> <span class="n">t</span> <span class="n">m</span> <span class="ow">=></span> <span class="n">m</span> <span class="nb">()</span>
<span class="nf">ui</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">el</span> <span class="s">"h1"</span> <span class="o">$</span> <span class="n">text</span> <span class="s">"Animals (Reflex)"</span>
<span class="n">myInput</span> <span class="ow"><-</span> <span class="n">el</span> <span class="s">"p"</span> <span class="o">$</span> <span class="n">textInput</span> <span class="n">def</span>
<span class="n">evStart</span> <span class="ow"><-</span> <span class="n">getPostBuild</span>
<span class="kr">let</span> <span class="n">evs</span> <span class="ow">=</span> <span class="p">[</span> <span class="nb">()</span> <span class="o"><$</span> <span class="n">_textInput_input</span> <span class="n">myInput</span> <span class="p">,</span> <span class="n">evStart</span> <span class="p">]</span>
<span class="kr">let</span> <span class="n">evCode</span> <span class="ow">=</span> <span class="n">tagPromptlyDyn</span> <span class="p">(</span><span class="n">value</span> <span class="n">myInput</span><span class="p">)</span> <span class="p">(</span><span class="n">leftmost</span> <span class="n">evs</span><span class="p">)</span>
<span class="n">evResponse</span> <span class="ow"><-</span> <span class="n">performRequestAsync</span> <span class="o">$</span> <span class="n">queryAnimals</span> <span class="o"><$></span> <span class="n">evCode</span>
<span class="kr">let</span> <span class="n">evResult</span> <span class="ow">=</span> <span class="n">fromJust</span> <span class="o">.</span> <span class="n">decodeXhrResponse</span> <span class="o"><$></span> <span class="n">evResponse</span>
<span class="n">dynAnimals</span> <span class="ow">::</span> <span class="p">(</span><span class="kt">Dynamic</span> <span class="n">t</span> <span class="p">[</span><span class="kt">Animal</span><span class="p">])</span> <span class="ow"><-</span> <span class="n">holdDyn</span> <span class="kt">[]</span> <span class="n">evResult</span>
<span class="kr">_</span> <span class="ow"><-</span> <span class="n">el</span> <span class="s">"span"</span> <span class="o">$</span> <span class="n">simpleList</span> <span class="n">dynAnimals</span> <span class="n">displayAnimal</span>
<span class="n">return</span> <span class="nb">()</span>
<span class="nf">queryAnimals</span> <span class="ow">::</span> <span class="kt">Text</span> <span class="ow">-></span> <span class="kt">XhrRequest</span> <span class="nb">()</span>
<span class="nf">queryAnimals</span> <span class="n">code</span> <span class="ow">=</span> <span class="kt">XhrRequest</span> <span class="s">"GET"</span> <span class="p">(</span><span class="s">"http://localhost:3000/api/animals/"</span> <span class="o"><></span> <span class="n">code</span><span class="p">)</span> <span class="n">def</span>
<span class="nf">displayAnimal</span> <span class="ow">::</span> <span class="kt">MonadWidget</span> <span class="n">t</span> <span class="n">m</span> <span class="ow">=></span> <span class="kt">Dynamic</span> <span class="n">t</span> <span class="kt">Animal</span> <span class="ow">-></span> <span class="n">m</span> <span class="nb">()</span>
<span class="nf">displayAnimal</span> <span class="n">dynAnimal</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">imgSrc</span> <span class="ow">=</span> <span class="p">(</span><span class="o"><></span><span class="p">)</span> <span class="s">"http://localhost:3000/img/"</span> <span class="o">.</span> <span class="n">animalImage</span> <span class="o"><$></span> <span class="n">dynAnimal</span>
<span class="kr">let</span> <span class="n">imgAttrs0</span> <span class="ow">=</span> <span class="p">(</span><span class="s">"width"</span> <span class="o">=:</span> <span class="s">"320"</span><span class="p">)</span> <span class="o"><></span> <span class="p">(</span><span class="s">"height"</span> <span class="o">=:</span> <span class="s">"240"</span><span class="p">)</span>
<span class="kr">let</span> <span class="n">imgAttrs</span> <span class="ow">=</span> <span class="p">((</span><span class="o"><></span><span class="p">)</span> <span class="n">imgAttrs0</span><span class="p">)</span> <span class="o">.</span> <span class="p">(</span><span class="o">=:</span><span class="p">)</span> <span class="s">"src"</span> <span class="o"><$></span> <span class="n">imgSrc</span>
<span class="n">el</span> <span class="s">"div"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">el</span> <span class="s">"p"</span> <span class="o">$</span> <span class="n">dynText</span> <span class="o">$</span> <span class="n">animalType</span> <span class="o"><$></span> <span class="n">dynAnimal</span>
<span class="n">elDynAttr</span> <span class="s">"img"</span> <span class="n">imgAttrs</span> <span class="o">$</span> <span class="n">dynText</span> <span class="n">imgSrc</span></code></pre>
<p>Reflex est un projet assez ambitieux, censé permettre de développer tout type d'interfaces utilisateur et pour de nombreuses plateformes différentes. Il a l'avantage d'utiliser le langage Haskell. En revanche, il est assez compliqué à prendre en main. Programmer directement en FRP nécessite un vrai apprentissage ainsi qu'une bonne compréhension des foncteurs, applicatives et monades. Enfin, il faut structurer son code soigneusement mais soi-même.</p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>Elm, Purescript, Miso et Reflex permettent de développer des applis web tout en profitant des avantages de la programmation fonctionnelle. Ces outils facilitent effectivement la validation de code et le refactoring, et réduisent voire suppriment les erreurs au runtime.</p>
<p>Le principal inconvénient réside dans le choix parmi ces outils, qui nécessite de faire un compromis entre simplicité et flexibilité. Elm et Purescript sont de bonnes options pour du web « pur et dur ». Miso et Reflex sont plutôt à réserver à des développeurs Haskell expérimentés. </p>
<p>Enfin, en dehors d'Haskell, il peut être intéressant de considérer d'autres outils comme ClojureScript, Ocsigen… </p>
</div><div><a href="https://linuxfr.org/news/developpement-web-frontend-en-haskell-elm-et-purescript.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/115105/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/developpement-web-frontend-en-haskell-elm-et-purescript#comments">ouvrir dans le navigateur</a>
</p>
nokomprendoBenoît SibaudDavy Defaudpalm123https://linuxfr.org/nodes/115105/comments.atomtag:linuxfr.org,2005:Diary/380032018-06-29T02:19:35+02:002018-06-29T02:19:35+02:00le style fonctionnel en vidéo (nix, nixos, haskell...), la suite...Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Bon bah, j'ai continué les vidéos du journal précédent (<a href="//linuxfr.org/users/nokomprendo-3/journaux/le-style-fonctionnel-en-videos-nix-nixos-haskell">https://linuxfr.org/users/nokomprendo-3/journaux/le-style-fonctionnel-en-videos-nix-nixos-haskell</a>).</p>
<p>Au passage, je suis passé sur framagit et j'ai ajouté un formatage plus propre des articles via hakyll (<a href="https://nokomprendo.frama.io/tuto_fonctionnel">https://nokomprendo.frama.io/tuto_fonctionnel</a>).</p>
<p>Les nouveaux sujets :<br>
- Personnaliser une image Docker de NixOS pour de l'intégration continue<br>
- Déployer un blog avec Hakyll, Gitlab-ci, Nix<br>
- Nix, programmation et loi de Murphy<br>
- Créer et partager des paquets Nix<br>
- Archlinux vs Voidlinux<br>
- Migrer un système NixOS (de 17.09 à 18.03)<br>
- Développement web en Haskell<br>
- Développement/déploiement web (apache, php, postgresql) avec nixops<br>
- Liaison de fonction dynamique (ou pas)<br>
- Curryfication et évaluation partielle (javascript/haskell)<br>
- Développer avec Nix en composant des projets C++/Python</p>
<div><a href="https://linuxfr.org/users/nokomprendo-3/journaux/le-style-fonctionnel-en-video-nix-nixos-haskell-la-suite.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/114811/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nokomprendo-3/journaux/le-style-fonctionnel-en-video-nix-nixos-haskell-la-suite#comments">ouvrir dans le navigateur</a>
</p>
nokomprendohttps://linuxfr.org/nodes/114811/comments.atomtag:linuxfr.org,2005:Diary/379012018-04-19T22:01:13+02:002018-04-19T22:01:13+02:00Le mode histoire de Nikki And The Robots libéréLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Bonjour,</p>
<p>L’équipe derrière Nikki and the robots a enfin pris le temps de rendre le mode histoire disponible sous licence libre avec le reste du jeu. La décision avait déjà été plus ou moins prise depuis longtemps (le système d’achat du mode histoire étant de toutes façons hors ligne depuis un moment) mais ça n’avait pas été fait jusque maintenant.</p>
<p>Un membre de l’équipe a aussi pris le temps d’adapter le système de build et de me guider pour faire un paquet AUR qui construit le jeu depuis les sources: <a href="https://aur.archlinux.org/packages/nikki">https://aur.archlinux.org/packages/nikki</a></p>
<p>Pour ceux qui ne connaissent pas, le jeu est un jeu de plateforme entièrement libre (seul le mode histoire était vendu sous licence restrictive et il vient de rejoindre le dépôt principal) dans lequel on guide un personnage déguisé en chat qui doit activer un certain nombres d’interrupteurs, souvent à l’aide de robots.<br>
Il y a aussi des batteries à collecter sur les niveaux, et la résolution est aussi chronométrée ce qui augmente la rejouabilité.</p>
<p>C’est un jeu vraiment très sympa, petit défaut pas de support manette malheureusement. Si quelqu’un maitrise haskell ce serait hyper cool de faire une PR qui ajoute ça. De même, ceux qui savent faire je vous encourage à empaqueter ça pour votre distribution.</p>
<p>Malheureusement le serveur pour partager des niveaux faits par la communauté est hors-ligne également.<br>
Il existe une archive des niveaux communautaires sur github: <a href="https://github.com/nikki-and-the-robots/nikki-levels">https://github.com/nikki-and-the-robots/nikki-levels</a> (également packagé dans AUR: <a href="https://aur.archlinux.org/packages/nikki-levels-git">https://aur.archlinux.org/packages/nikki-levels-git</a>)<br>
Je ne sais pas si le code du serveur de niveaux est disponible.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6e696b6b692d616e642d7468652d726f626f74732f6e696b6b692d776562736974652d6173736574732f7261772f6d61737465722f696d672f73637265656e73686f742d30352e706e67/screenshot-05.png" alt="https://github.com/nikki-and-the-robots/nikki-website-assets/raw/master/img/screenshot-05.png" title="Source : https://github.com/nikki-and-the-robots/nikki-website-assets/raw/master/img/screenshot-05.png"></p>
<p>Cette nouvelle version contenant le mode histoire est estampillée 1.1.1.</p>
<p>Un grand merci à l’équipe derrière le jeu pour la libération du mode histoire et l’aide à l’empaquetage.</p><div><a href="https://linuxfr.org/users/mcmic/journaux/le-mode-histoire-de-nikki-and-the-robots-libere.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/114280/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/mcmic/journaux/le-mode-histoire-de-nikki-and-the-robots-libere#comments">ouvrir dans le navigateur</a>
</p>
MCMichttps://linuxfr.org/nodes/114280/comments.atom