Haskell en 15 minutes

As of March 2020, School of Haskell has been switched to read-only mode.

Introduction

Haskell est un langage fonctionnel, pur, non-strict, typé statiquement.

Fonctionnel

Contrairement à un langage impératif, on ne définit pas une séquence d'instructions à exécuter, mais une expression à évaluer.

Les fonctions sont des valeurs comme les autres (fonctions d'ordre supérieur) : les fonctions peuvent prendre comme argument d'autres fonctions et renvoyer une nouvelle fonction.

Pur

Pas d'effet de bord : données immuables (les "variables" sont constantes), pas d'I/O caché (pas de communication avec le monde extérieur : écran, clavier, disque, réseau...).

Non-strict

Lazy : les calculs sont effectués uniquement lorsque leurs résultats sont nécessaires.

Typé statiquement

Tous les types sont connus lors de la compilation. Il n'y a pas de coercion implicite (entier non signé casté en entier signé, entier interprété comme une valeur booléenne...).

Grâce au système d'inférence de types (le compilateur est capable d'inférer le type des valeurs utilisées), le code peut être aussi concis que dans un langage typé dynamiquement, mais avec la sécurité d'un typage statique en plus.

Le type (la signature) d'une fonction permet de savoir dans une certaine mesure ce qu'elle fait, et (ce qui est plus important) ce qu'elle ne fait pas.

Les fonctions et les types peuvent être polymorphiques, ce qui favorise la ré-utilisation de code.

Outils

  • ghc : le compilateur Haskell le plus répandu.
  • ghci : interpréteur Haskell.
  • runhaskell : pour exécuter des programmes Haskell sans les compiler.
  • cabal : système de build et de gestion de packages.
  • hackage : collection de packages Haskell (un package contient un ou plusieurs programmes/librairies).
  • hoogle : moteur de recherche de packages/librairies/fonctions/types Haskell.
  • Haskell Platform : rassemble le compilateur ghc (y compris ghci et runhaskell), cabal et une sélection de packages.

Syntaxe

La syntaxe Haskell est élégante et concise, mais fort différente de celles de langages plus répandus.

Quelques types de base

x1 = 34      -- nombre entier
x2 = 1.6     -- nombre fractionnel
x3 = 'z'     -- caractère
x4 = False   -- booléen

Listes

-- Il y a deux moyens de créer une liste :
-- 1. création d'une liste vide
emptyList = []
-- 2. création d'une liste à partir d'un élément et d'une liste
prefixedList = 'b' : emptyList
-- Syntactic sugar
s123 = [1, 2, 3] -- équivalent à 1:2:3:[]
sabc = "abc" -- équivalent à 'a':'b':'c':[]

Tuples

t1 = (1, 2)            -- paire d'entiers
t2 = (3, 'a', "alpha") -- triplet comprenant un entier, un caractère et une string

Fonctions

-- fonctions unaires
plus2 x = x + 2

-- fonctions binaires
sumOfSquares x y = x * x + y * y

-- application de fonctions (pas de parenthèses) :
c = sumOfSquares 2.3 6.5

Pattern matching et pattern guards

contains []     e             = False
contains (x:xs) e | x == e    = True
                  | otherwise = contains xs e

Let ... in

-- l'indentation est importante :
f x y = let a = 3.2
            b = 1.9
        in a * x - b * y

... where

f' x y = a * x - b * y where  -- l'apostrophe est autorisée dans les noms
    a = 3.2                   -- de variables
    b = 1.9

if ... then ... else

contains' []     _ = False                           -- _ est un wildcard
contains' (x:xs) e = if x == e then True
                               else contains' xs e

Fonctions et opérateurs

Un opérateur (+ par exemple) est une fonction comme une autre, avec deux petites différences :

  • leur nom est composé des caractères suivants : # $ % & * + . / < = > ? @ \ ^ | - ~
  • par défaut, ils sont appliqués entre le premier et le deuxième argument ; pour utiliser la syntaxe préfixe (c'est-à-dire la syntaxe d'application de fonction), il faut les entourer de parenthèses.
a  = 1.2 + 3.4
a' = (+) 1.2 3.4

Les fonctions peuvent aussi être appliquées entre le premier et le deuxième argument ; il suffit d'entourer leur noms de backticks (` ) :

found  = contains [1..7] 5
found' = [1..7] `contains` 5

Type annotations

-- x1 est un Int
x1 :: Int
x1 = 34

-- plus2 est une fonction (polymorphique) de a vers a
-- le type a doit faire partie de la classe Num
plus2 :: Num a => a -> a
plus2 x = x + 2

-- contains est une fonction qui prend comme arguments
-- 1) une liste de a (une liste d'éléments de type a)
-- 2) un a (une valeur de type a)
-- et renvoie un booléen
-- le type a doit faire partie de la classe Eq
contains :: Eq a => [a] -> a -> Bool
contains []     _             = False
contains (x:xs) e | x == e    = True
                  | otherwise = contains xs e

Un programme d'exemple

Voici un exemple complet qui illustre également des éléments de syntaxe ignorés dans la section précédente (import, do, <-, case ... of) ainsi que le type Maybe.

import Data.Char (toUpper, toLower) -- importe les fonctions toUpper et
                                    -- toLower du module Data.Char
main :: IO ()
main = do               -- do marque le début d'une séquence d'opérations
    input <- getLine    -- <- associe à input le résultat de getLine
    putStrLn (caesar input)
    main

caesar :: String -> String
caesar = map (toUpper . rot13 . toLower)

rot13 :: Char -> Char
rot13 x = case lookup x table of
    Nothing -> x
    Just r  -> r
  where
    table = zip alphabet alphabet13
    alphabet = ['a'..'z']
    alphabet13 = drop 13 alphabet ++ take 13 alphabet

Haskell en plus de 15 minutes