TD 3

Sommaire de documents

Cet exercice propose de manipuler et d’afficher une représentation de sommaire qui structure les différentes parties d’un document. Cette représentation est implémentée par les types suivants :

data Section = Section String [Section]
type Sommaire = [Section]
type ou data ?

Il y a plusieurs différences entre data et type en Haskell.

  • data définit un nouveau type distinct, avec ses propres constructeurs. Ces types supportent du filtrage de motif et peuvent définir des types plus complexes.
  • type définit un synonyme de type, un alias pour un type existant. Il n’y a pas de constructeur et cela permet essentiellement de rendre du code existant plus clair.

On utilise data quand :

  1. on doit définir un type avec ses propres constructeurs ;
  2. on a besoin d’utiliser du filtrage de motif ;
  3. on veut un type qui peut représenter plusieurs possibilités (Either, Maybe…).

On utilise type quand :

  1. on veut simplifier des déclarations de type ;
  2. on veut donner des noms concrets à des types existants.

Le type Section t som représente une section avec un titre t et le sommaire de ses sous-parties som. Le type Sommaire est donc simplement une liste de Section.

Étant donné un sommaire de la forme :

lyah :: Sommaire
lyah = [Section "Introduction" [
         Section "About this tutorial" [],
         Section "So what's Haskell?" [],
         Section "What you need to dive in" []],
       Section "Starting Out" [
         Section "Ready, set, go!" [],
         Section "Baby's first functions" [],
         Section "An intro to lists" [],
         Section "Texas ranges" [],
         Section "I'm a list comprehension" [],
         Section "Tuples" []]
       ]

On veut pouvoir afficher une chaîne de caractères qui affiche le sommaire de façon numérotée comme ceci :

1 Introduction
..1.1 About this tutorial
..1.2 So what's Haskell?
..1.3 What you need to dive in
2 Starting Out
..2.1 Ready, set, go!
..2.2 Baby's first functions
..2.3 An intro to lists
..2.4 Texas ranges
..2.5 I'm a list comprehension
..2.6 Tuples

1. Transformer sections et sommaires en chaînes de caractères

On implémente deux fonctions (mutuellement récursives) :

  1. sommaireToStrings :: Sommaire -> [String], qui transforme un sommaire en la liste des lignes utilisées pour son affichage ;
  2. sectionToStrings :: Section -> [String] qui transforme une section en la liste des lignes utilisées pour son affichage.
sommaireToStrings :: Sommaire -> [String]
sommaireToStrings = concatMap sectionToStrings

sectionToStrings :: Section -> [String]
sectionToStrings (Section t som) = t : sommaireToStrings som

Le principe, c’est que sommaireToStrings va appliquer sectionToStrings à chaque élément de son sommaire, et concaténer les résultats (sinon, on se retrouve avec des listes de listes de listes).

2. Numérotation des sections et sommaires

Tout ceci, c’est très bien, mais on n’a pas de numérotation des éléments.

On va donc procéder de la même manière que précédemment, mais en ajoutant la numérotation. On aura deux fonctions :

  1. numSommaire :: String -> Sommaire -> Sommaire, qui va renvoyer un nouveau sommaire dont tous les titres de sections sont modifiés pour faire figurer devant une chaîne de caractères et le numéro ;
  2. numSection :: String -> Int -> Section -> Section, qui va renvoyer une nouvelle section avec un titre obtenu en ajoutant une chaîne et un numéro, et un sommaire précédé de ".." pour les décaler dans l’affichage.
numSommaire :: String -> Sommaire -> Sommaire
numSommaire s som = zipWith (numSection s) [1..] som
-- numSommaire s = zipWith (numSection s) [1..] en eta-reduite

numSection :: String -> Int -> Section -> Section
numSection s k (Section t som) = let
                                 t'   = s ++ show k ++ " " ++ t
                                 s'   = ".." ++ s ++ show k ++ "."
                                 som' = numSommaire s' som
                                 in (Section t' som')

Quand on est face à un sommaire, on utilise la liste [1..] en prime pour avoir la numérotation et on zippe avec la fonction numSection, qui va numéroter chaque section. La numérotation d’une section est le numéro devant le titre et ensuite on rappelle la numérotation sur le sommaire de la section, avec une chaîne de départ décalée et pré-numérotée.

Si on reprend notre exemple de tout à l’heure, on a :

numSommaire "" lyah --> zipWith (numSection "") [1..] lyah

--> numSection "" 1 Section "Introduction" [Section "About this tutorial" [], ...]

   --> t' = "1 Introduction"
       s' = "..1."
       som' = numSommaire "..1." [Section "About this tutorial" [], ...]

       --> zipWith (numSection "..1.") [1..] [Section "About this tutorial" [], ...]

         --> t' = "..1.1 About this tutorial"
             après la liste est vide
         
      <-- Section "1 Introduction" [Section "..1. About this tutorial" [] ...]

etc

3. Afficher le sommaire

On va écrire une fonction showSommaire :: Sommaire -> String qui numérote un sommaire puis le transforme en chaîne de caractères.

On va utiliser la fonction unlines :: [String] -> String qui concatène une liste de chaînes avec des sauts de lignes.

showSommaire :: Sommaire -> String
showSommaire = unlines . sommaireToStrings . numSommaire ""

Si on affiche le résultat sur notre exemple, on a bien :

ghci> putStrLn (showSommaire lyah)
1 Introduction
..1.1 About this tutorial
..1.2 So what's Haskell?
..1.3 What you need to dive in
2 Starting Out
..2.1 Ready, set, go!
..2.2 Baby's first functions
..2.3 An intro to lists
..2.4 Texas ranges
..2.5 I'm a list comprehension
..2.6 Tuples