La suite GHCup fournit
l’essentiel de ce qu’il faut pour développer en Haskell, en particulier
les outils Stack et Cabal. En prime, on bénéficie du serveur de langage
HLS qui
fonctionne très bien avec Neovim.1 Outre le compilateur GHC
(Glasgow Haskell Compiler), on dispose des programmes
runhaskell
et runghc
pour exécuter des scripts
Haskell directement, c’est-à-dire sans passer par une étape de
compilation.
Un projet se définit par un ensemble de “packages”, qui sont
eux-mêmes composés d’un ensemble de modules qui constituent l’essentiel
du code Haskell. Stack et Cabal permettent de gérer la construction et
le “packaging” d’un programme Haskell avec ses dépendances. Pour
construire un exécutable, il est nécessaire de définir une fonction
“main” dans le fichier source, et le nom du module doit être “Main” (ou
alors, on ajoute module Main where
en tout début de
fichier). Le cas échéant, GHC ne produira que des fichiers
.o
(fichier objet) et .hi
(fichier interface,
qui est l’équivalent des fichiers d’en-tête .h
en langage
C).
Voici un exemple simplifié de programme :
-- hello.hs
= putStrLn "Hello, World!" main
On peut le compiler très simplement avec
ghc -o hello hello.hs
; on obtient 3 fichiers en sortie,
dont un fichier exécutable dont le format dépend du système
d’exploitation :
» ls hello*
hello* hello.hi hello.hs hello.o
» ./hello
Hello, World!
On peut améliorer un peu ce programme initial en stockant la chaîne de caractères dans une variable :
-- hello.hs (v2)
= "Hello, World!"
msg = putStrLn msg main
Après compilation, on obtient exactement le même résultat que précédemment. Enfin, on peut créer une fonction simple qui se chargera d’assembler le message de bienvenue :
-- hello.hs (v3)
main :: IO ()
= putStrLn (f "World")
main = "Hello, " ++ x ++ "!" f x
Le serveur HLS devrait surligner que le type de cette fonction
f
est f :: [Char] -> [Char]
, ce qui est
équivalent à f :: String -> String
(une chaîne est une
suite ou liste de caractères). Après recompilation, le résultat produit
est identique. Comme dans les exemples précédents, le point d’entrée
reste main
(comme en C) mais cette fois-ci on spécifie que
son type est IO
, qui permet de gérer des actions
comme dans les langages impératifs.
Plutôt que de compiler systématiquement des scripts Haskell, il est également possible de spécifier dans une ligne “shebang”
#!/usr/bin/env stack
{- stack runghc -}
main = putStrLn "Hello, World!"
En fonction de la version de stack utilisée (ici, version 3.3.1),
GHCup téléchargera et installera automatiquement la version adéquate de
GHC (dans ce cas, version 9.10.2, qui correspond à la version LTS
Haskell 24.6). Il suffit ensuite de rendre ce fichier exécutable
(chmod +x hello.hs
sous Linux ou macOS) et de l’éxécuter
:
» ./hello.hs
Hello, World!
Notons qu’il est possible de préciser en plus de
stack script
ou stack runghc
le résolveur
Haskell à utiliser en dessous de la ligne “shebang”. Enfin, GHCup
installe automatiquement runhaskell
de sorte que la ligne
“shebang” ci-dessus peut-être remplacée par
#!/usr/bin/env runhaskell
si l’on souhiate travailler avec
la version de GHC définit comme courant sous GHCup (e.g., taper
ghcup set ghc 9.12.2
dans un terminal pour définir la
version par défaut à la dernière en date).
Jusqu’à présent on s’est contenté d’écrire de simples scripts Haskell et de les compiler ou de les exécuter. Ceci peut s’avérer utile pour de petits programmes, mais dans le cas où le nombre de dépendances augmente il devient préférable d’organiser le programme sous forme d’un ou plusieurs modules au sein d’un projet. Stack facilite la création et la gestion de projets.2
Reprenons l’exemple précédent, en supposant qu’il soit enregistré
dans un fichier Hello.hs
:
module Main ( main ) where
main :: IO ()
= putStrLn "Hello, World!" main
On ajoute un fichier de description du package,
package.yaml
, et on enregistre les deux fichiers dans un
répertoire “hello” :
name: hello-world
version: 1
dependencies: base
executable:
main: Hello.hs
Enfin, on ajoute le résolver snapshot: lts-24.0
dans un
fichier stack.yaml
. Ceci correspond à la version 9.10.2
de GHC. Avec ces deux fichiers de configuration, et le code source
Haskell, on dispose d’un projet simplifié mais complètement fonctionnel.
La commande stack new
va nous permettre d’automatiser
toutes ces étapes, comme on le verra plus tard. En attendant, si on
lance stack run
dans ce répertoire, le fichier source va
être compilé et l’exécutable sera lancé en fin de compilation :
» ls
Hello.hs package.yaml stack.yaml
» stack run
hello-world> configure (exe)
Configuring hello-world-1...
hello-world> build (exe) with ghc-9.10.2
Preprocessing executable 'hello-world' for hello-world-1...
Building executable 'hello-world' for hello-world-1...
[1 of 2] Compiling Main
[2 of 2] Compiling Paths_hello_world
[3 of 3] Linking .stack-work/dist/aarch64-osx/ghc-9.10.2/build/hello-world/hello-world
ld: warning: -U option is redundant when using -undefined dynamic_lookup
hello-world> copy/register
Installing executable hello-world in /Users/chl/cwd/sandbox/hello/.stack-work/install/aarch64-osx/f46c97d39f43c3a058be73f6626292c876f96e840215d7c2922c09d31bca3a15/9.10.2/bin
Hello, World!
Voici un exemple
de configuration à placer dans $HOME/.config/nvim/lsp
,
et à activer en ajoutant dans $HOME/.config/nvim/init.lua
l’instruction vim.lsp.enable({ "hls" })
.↩︎
Voir aussi An opinionated guide to Haskell in 2018 de Alexis King.↩︎