O tipo de uma variável ou expressão pode consistir de múltiplos tipos. Chamamos isso de tipo união. Por exemplo, quando atribuímos valores a uma mesma variável em partes diferentes de um if:
if 1 + 2 == 3
a = 1
else
a = "olá"
end
a # : Int32 | String
No final do if, a
terá o tipo Int32 | String
, ou seja, "a união de Int32 e String". Este tipo união é criado automaticamente pelo compilador. Em tempo de execução, a
só terá um tipo, é claro. Pode-se visualizar isso invocando o método class
:
# O tipo em tempo de execução
a.class # => Int32
O tipo em tempo de compilação pode ser visto usando typeof:
# O tipo em tempo de compilação
typeof(a) # => Int32 | String
Uma união pode consistir de um número de tipos grande e arbitrário. Quando invocamos um método em uma expressão cujo tipo seja um tipo união, todos os tipos nessa união precisam responder ao método, doutra forma haverá um erro em tempo de compilação. O tipo da chamada do método é o tipo união de todos os tipos de retorno desses métodos.
# to_s é definido para Int32 e String, ele retorna String
a.to_s # => String
a + 1 # Erro, porque String#+(Int32) não está definido
No caso geral, quando dois tipos T1
e T2
são combinados, o resultado é uma união de T1 | T2
. No entanto, existem alguns casos específicos onde o tipo resultante é diferente.
Se T1
e T2
estão sob a mesma hierarquia, e seu ancestral mais próximo em comum Parent
não for Reference
, Struct
, Int
, Float
e nem Value
, o tipo resultante é Parent+
. Isso é chamado de um tipo virtual, o que basicamente significa que o compilador verá esse tipo como sendo Parent
ou qualquer um de seus sub-tipos.
Por exemplo:
class Foo
end
class Bar < Foo
end
class Baz < Foo
end
bar = Bar.new
baz = Baz.new
# Aqui o tipo de foo será Bar | Baz,
# mas uma vez que tanto Bar quanto Baz herdam de Foo,
# o tipo resultante é Foo+
foo = rand < 0.5 ? bar : baz
typeof(foo) # => Foo+
A união de duas tuplas de mesmo tamanho resultado em um tipo de tupla que possui a união dos tipos em cada posição.
Por exemplo:
t1 = {1, "oi"} # Tuple(Int32, String)
t2 = {true, nil} # Tuple(Bool, Nil)
t3 = rand < 0.5 ? t1 : t2
typeof(t3) # Tuple(Int32 | Bool, String | Nil)
A união de duas tuplas nomeadas com as mesmas chaves (não importa a ordem) resulta em um tipo de tupla nomeada que possui a união dos tipos em cada chave. A ordem das chaves será a mesma que a das chaves à esquerda.
Por exemplo:
t1 = {x: 1, y: "oi"} # Tuple(x: Int32, y: String)
t2 = {y: true, x: nil} # Tuple(y: Bool, x: Nil)
t3 = rand < 0.5 ? t1 : t2
typeof(t3) # NamedTuple(x: Int32 | Nil, y: String | Bool)