Track
/
Crystal
Crystal
/
Esercizi
/
Blocco con password
Blocco con password

Blocco con password

Esercizio di apprendimento

Introduzione

Crystal permette che una variabile abbia più tipi. Questo si chiama tipo unione. In Crystal è comune che il compilatore inferisca un tipo unione.

Note

A runtime, un tipo unione è comunque un unico tipo, anche se è composto da più tipi. Questo significa che se un tipo unione è formato da String e Int32, non sarà entrambi contemporaneamente. Sarà invece uno String o un Int32.

Un tipo unione si dichiara separando i tipi con una barra verticale (|). Spesso vengono racchiusi tra parentesi tonde, ma non è obbligatorio. Il tipo unione più comune è (T | Nil), dove T è un tipo; compare nei metodi che possono restituire Nil. In questo caso si dice anche che la variabile è nilable.

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

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

Non è limitato a due soli tipi: può contenerne quanti ne vuoi.

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

typeof vs Object#class

Ci sono due modi per ottenere il tipo di una variabile: usare typeof oppure Object#class. La differenza è che typeof restituisce il tipo di una variabile in fase di compilazione, mentre Object#class restituisce il tipo a runtime. Questo significa che, per esempio, se vuoi sapere se una variabile è un tipo unione, Object#class non può dirtelo: restituisce il tipo solo a runtime, e a runtime si tratta di un unico tipo.

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

Operazioni sui tipi unione

Poiché a runtime un tipo unione è un unico tipo, tutte le operazioni standard funzionano su di esso. Ma in fase di compilazione il compilatore deve sapere di quale tipo si tratta. Perciò, quando vuoi usare le operazioni specifiche di un tipo, devi impostare il codice in modo che possa essere solo uno dei tipi.

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

Crystal ha però un metodo specifico per i tipi unione: il metodo is_a?, che accetta un tipo come argomento e restituisce un valore booleano. Il metodo nil? è una scorciatoia per is_a?(Nil). Inserire il metodo is_a? in un'espressione di controllo indica al compilatore di quale tipo si tratta, garantendo così che sia proprio quel tipo. E in un ramo else sarà garantito che non è quel tipo.

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

Questo is_a? non si limita ad avere un singolo tipo come argomento: può ricevere anche un tipo unione. Può anche essere combinato con && per più tipi.

Note

Il metodo is_a?, se usato insieme a un'espressione di controllo, non può essere chiamato su una variabile di istanza o una variabile di classe. In quel caso bisogna prima assegnarle a una variabile locale.

as

Un modo per trasformare un tipo unione in un unico tipo è fare in modo che un ramo possa essere eseguito solo se il tipo è un tipo specifico. Un altro approccio è usare il metodo as. Questo trasforma un tipo unione in un unico tipo eseguendo un controllo a runtime. Se il tipo non è quello previsto, viene sollevata un'eccezione.

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

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

Questo approccio è pensato solo per quando sei sicuro che il tipo sia quello previsto, oppure per quando vuoi sollevare un'eccezione se non lo è.

Usarlo con una configurazione inadeguata può portare a comportamenti inattesi.

as?

as? funziona in modo molto simile a as, ma restituisce nil invece di sollevare un'eccezione se il tipo non è quello previsto. Questo significa che restituisce un tipo unione formato dal tipo previsto e da Nil.

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

foo.as?(Int32) # => nil

Scorciatoia per i tipi nilable

Nilable significa che una variabile può essere un certo tipo oppure Nil. Questo si può scrivere come (T | Nil). Ma dato che i tipi nilable sono piuttosto comuni, esiste una scorciatoia: T?.

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

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

Istruzioni

L'azienda per cui lavori sta per lanciare il suo nuovissimo smartphone, chiamato Smarty 5: presenta un sistema di fotocamere completamente nuovo, un nuovo design e un processore rivoluzionario che gira su un nuovo sistema operativo chiamato smartyOS.

Il nuovo processore è abbastanza potente da gestire password più sicure che mai, e l'azienda ha deciso di sfruttarlo a proprio vantaggio. Ti hanno chiesto di implementare per il telefono un sistema di blocco tramite password, che permetta all'utente di impostare una password e poi di verificare se una password data è corretta.

Caution

Il sistema di password trattato in questo esercizio non è sicuro ed è usato solo a scopo didattico. NON dovrebbe essere usato in nessuna applicazione reale.

1. Impostare una password

Il telefono permette agli utenti di usare diversi tipi di password, da una semplice password numerica a una password alfanumerica più sicura, e persino una password che memorizza l'impronta digitale dell'utente.

Questi diversi tipi di password usano tipi diversi: la password numerica usa un Int32, la password alfanumerica usa una String, e la password con impronta digitale usa un Float64.

Implementa la classe PasswordLock con un inizializzatore che accetta come argomento uno qualsiasi dei tre tipi di password e lo memorizza in una variabile di istanza chiamata @password.

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

2. Crittografare la password

L'azienda ha deciso di crittografare la password in modo che non sia memorizzata in chiaro, e ti ha chiesto di implementare un metodo per crittografarla.

Ogni tipo di password ha il suo modo unico di essere crittografato:

  • Password Int32: la password viene divisa per due e arrotondata all'intero più vicino.
  • Password String: la password viene invertita.
  • Password Float64: la password viene moltiplicata per quattro.

Implementa un metodo di istanza chiamato encrypt che non accetta argomenti e modifica la variabile di istanza @password in modo che sia crittografata.

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

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

3. Verificare se una password è corretta

L'azienda ti ha anche chiesto di implementare un metodo che verifica se una password data è corretta.

Vogliono che il metodo restituisca nil se la password non è corretta e "Unlocked" se la password è corretta.

Implementa un metodo di istanza chiamato unlock? che accetta come argomento la password da verificare. Il metodo deve restituire nil se la password non è corretta e "Unlocked" se la password è corretta.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Crystal Exercism

Vuoi iniziare Blocco con password?

Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.