Tracce
/
Crystal
Crystal
/
Programma
/
Tipo unione
Ti

Tipo unione in Crystal

1 esercizio

Informazioni su Tipo unione

In Crystal una variabile può essere di più tipi diversi. Questo si chiama tipo unione. In Crystal, è comune che sia il compilatore a inferire un tipo unione.

Note

Un tipo unione resta comunque un unico tipo a runtime, anche se è composto da più tipi. Questo significa che, se un tipo unione è formato da String e Int32, non sarà entrambi allo stesso tempo. Sarà invece o uno String o un Int32.

Un tipo unione si dichiara separando i tipi con una pipe (|). Spesso vengono racchiusi tra parentesi, ma non è obbligatorio. Il tipo unione più comune è (T | Nil), dove T è un tipo, e si può incontrare 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 lo restituisce a runtime. Questo significa che, se per esempio vuoi sapere se una variabile è un tipo unione, Object#class non potrà dirtelo, perché restituirà solo il tipo a runtime, che è un unico tipo.

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

Operazioni sui tipi unione

Poiché un tipo unione è un unico tipo a runtime, tutte le operazioni standard funzionano su di esso. Quando però si compila il codice, il compilatore deve sapere di quale tipo si tratta. Per questo, quando vuoi usare le operazioni specifiche di un tipo, il codice deve essere strutturato in modo che possa essere solo uno di quei tipi.

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

Crystal ha un metodo apposta per i tipi unione: il metodo is_a?, che prende un tipo come argomento e restituisce un booleano. Il metodo nil? è una scorciatoia per is_a?(Nil). Mettere il metodo is_a? in un'espressione di controllo dirà al compilatore di quale tipo si tratta e garantirà quindi che sia proprio quel tipo. In un ramo else, invece, garantirà che non sia quel tipo.

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

Questo is_a? non si limita a un singolo tipo come argomento: può ricevere anche un tipo unione. Si può inoltre combinare con && per più tipi.

Note

Quando lo si usa insieme a un'espressione di controllo, il metodo is_a? non può essere chiamato su una variabile di istanza o su una variabile di classe. In questi casi 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 è quello 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 atteso, 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 atteso, o per quando vuoi sollevare un'eccezione se non lo è.

Usarlo con una struttura non corretta 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 atteso. Questo significa che restituisce un tipo unione tra il tipo atteso e Nil.

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

foo.as?(Int32) # => nil

Forma abbreviata per i tipi nilable

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

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

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

Inferenza dei tipi unione

Il compilatore inferisce un tipo unione quando non è sicuro di quale tipo si tratti. Per esempio, nel codice seguente il compilatore non sa di che tipo sia foo, dato che può essere sia uno String sia un Int32.

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

Questa inferenza avviene automaticamente, e ci sono altri scenari in cui, per esempio, il compilatore inferisce che un metodo restituisce un tipo unione.

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Tipo unione

La pratica è bloccata

Sblocca un altro esercizio per esercitarti su Tipo unione