Parcours
/
Crystal
Crystal
/
Exercices
/
Verrou à mot de passe
Verrou à mot de passe

Verrou à mot de passe

Exercice d'apprentissage

Introduction

Crystal permet à une variable de contenir plusieurs types à la fois. C'est ce qu'on appelle un type union. En Crystal, il est courant qu'un type union soit inféré par le compilateur.

Note

Un type union reste un seul type à l'exécution, même s'il est composé de plusieurs types. Cela signifie que si un type union est construit à partir de String et Int32, il ne sera pas les deux en même temps. Il sera soit un String, soit un Int32.

Un type union se déclare en séparant les types par une barre verticale (|). On les met souvent entre parenthèses, mais ce n'est pas obligatoire. Le type union le plus courant est (T | Nil), où T est un type, ce qui peut apparaître dans des méthodes susceptibles de renvoyer Nil. On dit alors que la variable est nilable.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

Ce n'est pas limité à deux types : on peut en mettre autant qu'on veut.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof vs Object#class

Il y a deux façons d'obtenir le type d'une variable. Soit en utilisant typeof, soit en utilisant Object#class. La différence est que typeof renvoie le type d'une variable à la compilation, tandis que Object#class renvoie le type à l'exécution. Cela signifie que si tu veux savoir si une variable est un type union, par exemple, Object#class ne pourra pas te le dire, car il ne renvoie le type qu'à l'exécution, où il s'agit d'un seul type.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

Opérations sur les types union

Comme un type union est un seul type à l'exécution, toutes les opérations standard fonctionnent dessus. Mais au moment de compiler le code, le compilateur doit savoir de quel type il s'agit. Le code doit donc être agencé de telle sorte qu'il ne puisse être que l'un des types lorsqu'on veut utiliser les opérations propres à un type.

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Crystal possède toutefois une méthode particulière pour les types union : la méthode is_a?, qui prend un type en argument et renvoie un booléen. La méthode nil? est un raccourci pour is_a?(Nil). Placer la méthode is_a? dans une expression de contrôle indique au compilateur de quel type il s'agit, ce qui garantit que c'est bien ce type. Et dans une branche else, cela garantit que ce n'est pas ce type.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

Cet is_a? n'est pas limité à un seul type en argument : il peut aussi prendre un type union. Il peut également être combiné avec && pour plusieurs types.

Note

La méthode is_a?, lorsqu'on l'utilise conjointement avec une expression de contrôle, ne peut pas porter sur une variable d'instance ou une variable de classe. Il faut d'abord les affecter à une variable locale.

as

Une façon de transformer un type union en un seul type consiste à faire en sorte qu'une branche ne puisse être empruntée que si le type est un type précis. Une autre approche consiste à utiliser la méthode as. Celle-ci transforme un type union en un seul type en effectuant une vérification à l'exécution. Une exception est levée si le type n'est pas celui attendu.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

Cette approche n'est prévue que pour les cas où tu es sûr que le type est celui attendu, ou si tu veux lever une exception dans le cas contraire.

L'utiliser dans une configuration inappropriée peut entraîner un comportement inattendu.

as?

as? fonctionne de manière très similaire à as, mais renvoie nil au lieu de lever une exception si le type n'est pas celui attendu. Cela signifie qu'elle renvoie un type union composé du type attendu et de Nil.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

Raccourci nilable

Nilable signifie qu'une variable peut être soit d'un certain type, soit Nil. Cela s'écrit (T | Nil). Mais comme les types nilables sont relativement courants, il existe un raccourci : T?.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

Instructions

L'entreprise pour laquelle tu travailles s'apprête à lancer son tout nouveau smartphone, le Smarty 5, qui embarque un tout nouveau système de caméra, un nouveau design et un processeur révolutionnaire tournant sous un nouveau système d'exploitation appelé smartyOS.

Ce nouveau processeur est capable de gérer des mots de passe plus sécurisés que jamais, et l'entreprise a décidé d'en tirer parti. Elle t'a demandé de mettre en place un système de verrouillage par mot de passe pour le téléphone, qui permet à l'utilisateur de définir un mot de passe, puis de vérifier si un mot de passe donné est correct.

Caution

Le système de mots de passe utilisé dans cet exercice n'est pas sécurisé et ne sert qu'à des fins pédagogiques. Il ne doit PAS être utilisé dans de vraies applications.

1. Définis un mot de passe

Le téléphone permet aux utilisateurs d'utiliser plusieurs types de mots de passe différents, d'un simple mot de passe à chiffres à un mot de passe alphanumérique plus sécurisé, et même à un mot de passe qui stocke leur empreinte digitale.

Ces différents types de mots de passe utilisent des types différents : le mot de passe à chiffres utilise un Int32, le mot de passe alphanumérique utilise un String et le mot de passe par empreinte digitale utilise un Float64.

Implémente la classe PasswordLock avec un initialiseur qui prend en argument l'un des trois types de mots de passe et le stocke dans une variable d'instance appelée @password.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

2. Chiffre le mot de passe

L'entreprise a décidé de chiffrer le mot de passe pour qu'il ne soit pas stocké en clair et t'a demandé d'implémenter une méthode pour le chiffrer.

Chaque type de mot de passe se chiffre d'une manière qui lui est propre :

  • Mot de passe Int32 : le mot de passe est divisé par deux et arrondi à l'entier le plus proche.
  • Mot de passe String : le mot de passe est inversé.
  • Mot de passe Float64 : le mot de passe est multiplié par quatre.

Implémente une méthode d'instance appelée encrypt qui ne prend aucun argument et qui modifie la variable d'instance @password pour qu'elle soit chiffrée.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>

3. Vérifie si un mot de passe est correct

L'entreprise t'a aussi demandé d'implémenter une méthode qui vérifie si un mot de passe donné est correct.

Elle veut que la méthode renvoie nil si le mot de passe est incorrect et "Unlocked" si le mot de passe est correct.

Implémente une méthode d'instance appelée unlock? qui prend en argument le mot de passe à vérifier. La méthode doit renvoyer nil si le mot de passe est incorrect et "Unlocked" si le mot de passe est correct.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Crystal Exercism

Prêt à commencer Verrou à mot de passe ?

Inscris-toi sur Exercism pour apprendre et maîtriser Crystal avec 26 concepts142 exercices, et un vrai mentorat humain, le tout gratuitement.