Elm est un langage avec un typage statique. Cela signifie que les types des données sont connus à l’avance et que le compilateur vérifie que les valeurs correspondent bien aux types annoncés.

Les types primitifs de Elm

Les types primitifs de Elm sont les types disponibles par défaut dans le langage Elm.

Il s’agit de Char , String , Bool et number ( Int & Float ).

-- Char
'a'

-- String
"a"

"Hello"

"""
This is
a multiligne
String
"""

-- number
5

-- Float
3.5

-- Bool
True
Flase

Types Variables

Comme nous l’avons vue dans le chapitre “fonctions”, les Types Variables sont des types génériques qui peuvent être remplacés par n’importe quel types.

Ils doivent être écrit en minuscules.

On utilise souvent a, b, c … mais rien ne nous empêche d’utiliser des mots plus explicites : valeurs, chat …

Exemple avec la fonction List.reverse :

List.reverse : List a -> List a
-- On peut utiliser la fonction  List.reverse avec des String, des Int ...

-- List String -> List String
-- List Int -> List Int

La fonction List.reverse accepte n’importe quel type à la place de “a” mais d’après sa signature si par exemple on passe un String en paramètre, le resultat doit être un String, idem si c’est un Int, le résultat doit être un Int.

Types Variables contraints

Il s’agit des types number, appendable, compappend, comparable.

Ils peuvent prendre différents types mais parmi un choix restreint par Elm :

number = Int or Float
appendable = String or List a
compappend = String or List comparable
comparable = Int, Float, Char, String, lists and tuples of comparable values

Par exemple :

negate : number -> number

-- number ne peut être que un type Float ou un type Int

Type Maybe

Elm interdit la valeur null (ou undefined…) et a prévu un Union Type Maybe pour pallier ce problème.

Le type Maybe est définie avec deux balises : Just et Nothing qui doivent être écrit avec une majuscule. La valeur d’un type Maybe ne peut être que l’un des deux.

type Maybe a = Just a | Nothing

Une valeur de type Maybe peut avoir juste une valeur a (c’est à dire de n’importe quel type) soit n’a aucune valeur, c’est à dire “Rien”.

On l’utilise pour les fonctions qui sont susceptibles de ne pas renvoyer de valeur. Ainsi implémenté, une fonction ne provoquera pas d’erreur mais renvera soit une valeur de n’importe que type soit Nothing.

C’est le cas par exemple de la fonction get :

-- signature de la fonction "get" :
get : Int -> Array a -> Maybe a

Exemple d’utilisation de Maybe :

helloWorld : Maybe String -> String
helloWorld name =
    case name of
        Nothing ->
            "Hello, World!"

        Just aName ->
            "Hello, " ++ aName ++ "!"

Le complilateur nous oblige à traiter les cas d’erreurs.

Maybe est définie dans le module Maybe qui fourni plusieurs fonctions.

Types Aliases

Un alias de type est un nom personnalisé donné à un autre type existant. Cela permet une lecture plus “friendly” du code.

type alias Name = String
-- "Name" devient un type personnalisé de type String

-- Un type alias permet de raccourcir un autre type :
type alias Pet =
  { name: String
  , age: Int
  }
-- "Pet" devient un type personnalisé de type record

C’est un cas fréquent en Elm de donner un nom personnalisé à une structure de données de type record afin de raccourcir son écriture. C’est plus pratique si on doit utiliser ce record à plusieurs reprises dans son code.

Autre exemple :

type alias User =
  { name : String
  , bio : String
  }

hasDecentBio : User -> Bool
hasDecentBio user =
  String.length user.bio > 80

-- C'est plus court et plus lisible que d'écrire :
hasDecentBio : { name : String, bio : String } -> Bool
hasDecentBio user =
  String.length .bio > 80

On peut utiliser un type alias en tant que constructeur.

-- Création d'une variable de type "Pet" (qui est un record) et affectation des ses deux valeurs "name" et "age" :

dog = Pet "Puppy" 2

-- { name = "Puppy", age = 2 } : Pet

Union Types

Un “union type” est un type personnalisé constitué de valeurs prédéfinies.

type Answer = Yes | No

-- Le type Answer peut avoir uniquement l'état "Yes" ou "No"

respond : Answer -> String
respond answer =
    case answer of
      Yes ->
        ...
      No ->
        ...

-- La fonction "answer" prend en paramètre uniquement une des valeurs de l'union type "Answer" donc soit "Yes" soit "No"

Associer de l'information (Payload)

Les types d’union peuvent être associées à de l’information :

type Answer = Yes | No | Other String

Dans ce cas “Other” devra avoir une valeur String associée :

-- Les parenthèses sont nécessaires sinon Elm interprète qu'il s'agit de deux paramètres

respond (Other "Hello")

Autre exemple :

type Answer = Message Int String

-- Création d'une instance Message
Message 1 "Hello"

Imbrication

On peut imbriquer des unions types :

type OtherAnswer = DontKnow | Perhaps | Undecided

-- On imbrique l'union type "OtherAnswer" dans l'union type "Answer"
type Answer = Yes | No | Other OtherAnswer

-- Ici Other ne pourra prendre que les valeurs "DontKnow", "Perhaps" ou "Undecided"

respond (Other Perhaps)

Constructeur de fonctions

Comme tout est fonction daans Elm, les types d’union se comportent également comme des fonctions. Par exemple avec ce type :

-- création d'un union type "Answer"
type Answer = Message Int String

On créer un tag Message de cette manière :

Message 1 "Hello"

Il est aussi possible de faire de l’application partielle, comme avec n’importe quelle autre fonction. On appelle cela des constructeurs car vous pouvez vous en servir pour construire des types complets. Par exemple utiliser Message comme fonction pour construire (Message 1 “Hello”).

Variables de type

Il est aussi possible d’utiliser des variables de type (aussi appelées stand-in) dans la déclaration d’un union type :

type Answer a = Yes | No | Other a

C’est un Answer qui peut être utilisé avec différents types, comme Int ou String.

Par exemple, respond pourrait ressembler à ça :

respond : Answer Int -> String
respond answer =
    ...

Ici nous spécifions que le stand-in “a” devrait être de type Int en utilisant la signature Answer Int.

Nous serons ensuite capable d’appeler respond comme ceci :

respond (Other 123)

Mais respond (Other “Hello”) échouera car respond s’attend à recevoir un entier en lieu et place de a. Les parenthèses sont nécessaires pour que “Other 123” ne soit pas confondue avec des paramètres.

Cas d'usage

Dans Elm, les Unions Types sont fréquement utilisés avec une instruction case of qui est assez similaire aux switch case dans d’autres langages. On peut ainsi tester des valeurs et effectuer des actions en conséquence.

Par exemple dans les fonctions Update des applications Elm, on traite les messages provenant de la View et on effectue des actions selon le message reçu pour modifier le Model.

Sources:
https://azer.bike/journal/elm/
https://guide.elm-lang.org
https://www.elm-tutorial.org

Disclaimer:
Etant un total débutant dans le langage Elm et la programmation fonctionnel, il se peut que des incompréhensions ou des erreurs se soient glissées dans mes explications. Si vous en remarquez, merci de me les signaler pour que je puisse les corriger au plus vite.