Parcours
/
Elm
Elm
/
Exercices
/
Le suiveur
Le suiveur

Le suiveur

Exercice d'apprentissage

Introduction

Enregistrements

Les enregistrements sont des structures de données qui regroupent des informations liées entre elles sous des étiquettes. Ils sont similaires aux objets en JavaScript ou en Java, ou aux structs en C ou en Rust, mais avec quelques différences essentielles.

Crée des enregistrements

Les enregistrements se créent avec des accolades et leurs éléments sont séparés par des virgules.

firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

Le type d'un enregistrement se définit aussi avec une syntaxe similaire, sauf que les signes égal = sont remplacés par des deux-points :.

firefly : { name : String, creator : String, episodes : Int }
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

Répéter ces définitions de type est fastidieux, c'est pourquoi utiliser un alias de type pour partager la définition est une pratique courante.

type alias TvSeries =
    { name : String
    , creator : String
    , episodes : Int
    }

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

L'alias de type fournit aussi automatiquement une fonction de construction pour l'enregistrement. Cela surprend parfois, car en Elm les fonctions doivent commencer par une minuscule, mais les constructeurs de type sont une exception.

firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14

Accède aux champs d'un enregistrement

La principale façon d'accéder à la valeur d'un champ d'une instance d'enregistrement est d'utiliser la notation pointée, comme firefly.creator. Le compilateur Elm prend aussi en charge des fonctions d'accès spéciales qui commencent par un point ., comme .creator, et qui fonctionnent sur n'importe quel enregistrement ayant un champ de ce nom. C'est la deuxième exception à la règle selon laquelle les fonctions doivent commencer par une minuscule. En fait, les fonctions définies par l'utilisateur doivent commencer par une minuscule, mais les deux exemples que l'on vient de découvrir sont générés par le compilateur, pas par le programmeur.

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

firefly.name
    --> "Firefly"

.creator firefly
    --> "Joss Whedon"

Les enregistrements peuvent aussi être déstructurés dans des liaisons, grâce au filtrage par motif sur les enregistrements. La déstructuration fonctionne avec n'importe quel enregistrement qui possède des champs portant les noms concernés.

episodesCount : TvSeries -> Int
episodesCount { episodes } =
    episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
    let
        { name, creator } = show
    in
    { name = name
    , creator = creator
    , episodes = episodesCount show
    }

Mets à jour des enregistrements

Les enregistrements sont immuables, comme tout en Elm, donc une fois qu'un enregistrement est défini, les valeurs de ses champs ne peuvent plus jamais changer. Il existe une syntaxe spéciale pour créer une copie d'un enregistrement existant tout en modifiant un ou plusieurs de ses champs. Cette syntaxe utilise le symbole barre verticale | pour distinguer l'enregistrement d'origine et les champs qui vont changer, comme { oldRecord | field1 = newValue }.

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

updatedFirefly : TvSeries
updatedFirefly =
    { firefly | creator = "J Whedon", episodes = 15 }

Compare des enregistrements

Elm utilise l'[égalité structurelle][equality], ce qui signifie que deux instances du même enregistrement avec des valeurs identiques sont égales.

firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
    --> True

Enregistrements extensibles

Elm prend aussi en charge le typage structurel, ce qui signifie que si une fonction a besoin d'un enregistrement avec un champ x et un champ y, elle fonctionnera avec n'importe quel enregistrement possédant ces champs, comme des points 2D, des points 3D, des vaisseaux spatiaux, etc. Ces types d'enregistrement comportent une barre verticale | dans leur définition, comme { a | x : Float, y : Float }, et sont appelés « enregistrements extensibles » dans la terminologie d'Elm. Attention à la différence entre une barre verticale | dans une définition de type, qui est une définition d'enregistrement extensible ({ a | x : Int }), et une barre verticale | dans une instance d'enregistrement réelle, qui signifie que l'on met à jour certains champs de cet enregistrement.

point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
    sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
    { vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }

Instructions

Dans cet exercice, tu es un grand fan de sport et tu viens de découvrir une passion pour le basketball de la NBA. Comme tu débutes dans le basketball NBA, tu te plonges dans l'histoire de la NBA en suivant les équipes, les entraîneurs et leurs statistiques de victoires et de défaites, et en les comparant entre elles.

Comme tu n'as pas encore d'équipe favorite, tu vas aussi développer un algorithme pour déterminer s'il faut encourager une équipe en particulier. Tu as sept tâches pour t'aider à mettre au point ton algorithme root-for-a-team maison.

1. Définis le modèle

Définis l'alias de type Coach pour un enregistrement avec les deux champs suivants, dans cet ordre :

  • name : le nom de l'entraîneur, de type String.
  • formerPlayer : indique si l'entraîneur était un ancien joueur, de type Bool.

Définis l'alias de type Stats pour un enregistrement avec les deux champs suivants, dans cet ordre :

  • wins : le nombre de victoires, de type Int.
  • losses : le nombre de défaites, de type Int.

Définis l'alias de type Team pour un enregistrement avec les trois champs suivants, dans cet ordre :

  • name : le nom de l'équipe, de type String.
  • coach : l'entraîneur de l'équipe, de type Coach.
  • stats : les statistiques de l'équipe, de type Stats.

2. Crée une équipe

Tu veux communiquer avec du code tiers qui a la notion d'équipe, mais qui appelle ton code avec les paramètres dans un ordre différent. Ajoute une fonction createTeam avec la signature suivante String -> Stats -> Coach -> Team.

3. Remplace l'entraîneur

Les propriétaires de NBA étant impatients, tu as constaté que de mauvais résultats d'équipe entraînaient souvent le remplacement de l'entraîneur. Implémente la fonction replaceCoach qui prend le nouvel entraîneur et l'équipe en paramètres, et qui renvoie l'équipe avec le nouvel entraîneur :

let
    coach = { name = "Larry Bird", formerPlayer = True }
    stats = { wins = 58, losses =  24 }
    team = { name = "Indiana Pacers", coach = coach, stats =  stats }
    newCoach = { name = "Isiah Thomas", formerPlayer = True }
in
replaceCoach newCoach team
--> { name = "Indiana Pacers"
--  , coach = { name = "Isiah Thomas", formerPlayer = True }
--  , stats = { wins = 58, losses = 24 } }

4. Vérifie si tu dois encourager une équipe

À l'avenir, tu prévois de concevoir un algorithme compliqué pour savoir s'il faut encourager une équipe ou non, mais pour cette première tentative, tu as décidé qu'il faut encourager toute équipe qui a plus de victoires que de défaites.

Tu veux que cette fonction soit réutilisable dans d'autres contextes, alors utilise un enregistrement extensible dans la signature de type pour accepter n'importe quel enregistrement qui possède les champs pertinents. Pour que la fonction reste aussi concise que possible, tu veux utiliser le pattern matching pour récupérer les stats à partir du paramètre de la fonction. Implémente la fonction rootForTeam qui prend une équipe et renvoie True si tu dois encourager cette équipe ; sinon, renvoie False.

let
    spursCoach = { name = "Gregg Popovich", formerPlayer =  False }
    spursStats = { wins = 56, losses = 26 }
    spursTeam = { name = "San Antonio Spurs", coach = spursCoach, stats = spursStats }
in
rootForTeam spursTeam
--> True
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Elm Exercism

Prêt à commencer Le suiveur ?

Inscris-toi sur Exercism pour apprendre et maîtriser Elm avec 28 concepts110 exercices, et un vrai mentorat humain, le tout gratuitement.