Les types opaques sont une technique d'encapsulation en Elm.
Certains parlent aussi de masquage d'information, ou de dissimulation des détails d'implémentation.
On les utilise également couramment pour faire respecter une validation, avec le motif Parse, dont validate.
Par exemple, on peut avoir un type opaque EmailAddress, qui ne peut contenir que des adresses e-mail valides (contrairement à une String, qui peut contenir n'importe quoi).
Les types personnalisés ont une ou plusieurs variantes, et le compilateur génère automatiquement une fonction pour créer chaque variante, en utilisant le nom de la variante. C'est le seul moyen de créer des types personnalisés. Le compilateur permet aussi de déstructurer ces variantes.
type Maybe a
= Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
| Just a --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'
Par défaut, tout cela reste interne au fichier / module, et on utilise le mot-clé exposing pour choisir ce que l'on exporte.
Pour créer un type opaque, il suffit de choisir de ne pas exporter les variantes.
Quand tu fais cela, tu dois presque toujours fournir un autre moyen de créer et de récupérer les variantes, en exposant des fonctions personnalisées. Ces fonctions personnalisées te permettent d'appliquer une certaine logique lors de la création et de la récupération des variantes. Cela masque les détails d'implémentation internes du module, et signifie que tu peux modifier les détails d'implémentation autant que tu le souhaites, tant que les fonctions exposées restent identiques.
L'utilisation la plus courante des types opaques est probablement de faire respecter une validation, avec le motif Parse, dont validate.
Dans ce motif, on expose une fonction qui prend une entrée moins structurée / moins sûre du point de vue des types que ce que l'on souhaite, comme une String, puis on l'analyse pour obtenir un type plus structuré, comme EmailAddress, en renvoyant soit le type, soit une erreur (généralement Maybe ou Result).
Par exemple, dans le code ci-dessous, les modules externes ne peuvent créer un type EmailAddress qu'avec la fonction parseEmailAddress, donc quand tu manipules le type EmailAddress, tu es certain qu'il est valide.
module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )
-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress =
EmailAddress String
-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =
if isValidEmailAddress candidateEmailAddress then
Just (EmailAddress candidateEmailAddress)
else
Nothing
-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress emailAdress ) =
emailAdress
isValidEmailAddress: String -> bool
-- ...
Dans cet exercice, tu vas écrire un SecureTreasureChest générique (et même magique !) pour y ranger un trésor.
Les élèves les plus observateurs auront remarqué que le TreasureChest précédent n'était pas si sûr que ça !
Si tu utilisais la fonction getTreasure, il fallait fournir le mot de passe, mais tu pouvais quand même simplement déstructurer le type TreasureChest pour récupérer le trésor, sans avoir à connaître le mot de passe.
Pire encore, tu pouvais même récupérer le mot de passe de la même façon !
Corrigeons cela en utilisant un type opaque.
Définis un type personnalisé Password avec une seule variante.
Cette variante doit avoir une valeur String associée, pour le mot de passe.
Tu dois exposer le type Password, mais pas sa variante.
Cela fait de Password un type opaque, et signifie que tu ne peux plus utiliser la déstructuration pour obtenir le mot de passe.
Il n'est pas possible de vérifier si un type est opaque ou non ; en production, tu utiliserais elm-review (un outil d'analyse statique) pour le garantir.
Définis et expose une fonction createPassword, avec une annotation de type, qui prend une String et renvoie un Maybe Password.
Owasp recommande que les mots de passe comportent au moins 8 caractères, alors utilisons le motif Parse, don't validate pour faire respecter cet invariant.
Si le mot de passe fait moins de 8 caractères, createPassword doit renvoyer Nothing, sinon elle doit renvoyer Just Password.
Tu dois exposer le type SecureTreasureChest, mais pas sa variante.
Cela fait de SecureTreasureChest un type opaque, et signifie que tu ne peux plus utiliser la déstructuration pour obtenir le mot de passe ou le trésor.
Cette fonction doit prendre deux paramètres et avoir une annotation de type.
Password (pour le mot de passe)La fonction doit renvoyer un SecureTreasureChest.
Expose cette fonction, pour que d'autres modules puissent créer des instances de SecureTreasureChest.
Cette fonction doit prendre deux paramètres et avoir une annotation de type.
String (pour le mot de passe)SecureTreasureChest
Cette fonction doit vérifier la tentative de mot de passe fournie par rapport au Password contenu dans le SecureTreasureChest.
La fonction doit renvoyer un Maybe.
Si les mots de passe correspondent, renvoie Just avec la valeur générique du SecureTreasureChest (le trésor !)
Si les mots de passe ne correspondent pas, renvoie Nothing.
Expose cette fonction, pour que d'autres modules puissent récupérer le trésor dans un SecureTreasureChest, à condition de fournir le bon mot de passe.
Inscris-toi sur Exercism pour apprendre et maîtriser Elm avec 28 concepts110 exercices, et un vrai mentorat humain, le tout gratuitement.