유니언 타입
유니언 타입 (Union types)
변수가 여러 타입 중 하나를 가질 수 있을 때가 있죠. 이렇게 변수나 표현식의 타입이 여러 타입으로 구성될 수 있는 것을 유니언 타입(union type)이라고 해요.
출처: Crystal 공식 문서
본문
변수나 표현식의 타입은 여러 타입으로 구성될 수 있어요. 이것을 유니언 타입이라고 해요. 예를 들어 서로 다른 if 분기 안에서 같은 변수에 값을 할당할 때:
if 1 + 2 == 3
a = 1
else
a = "hello"
end
a # : Int32 | String
if가 끝나면 a는 Int32 | String 타입을 가지는데, "Int32와 String의 유니언"이라고 읽어요. 이 유니언 타입은 컴파일러가 자동으로 만들어요. 런타임에는 당연히 a는 한 타입만 가져요. 이는 class 메서드를 호출해 확인할 수 있어요.
# The runtime type
a.class # => Int32
컴파일 타임 타입은 typeof로 확인할 수 있어요.
# The compile-time type
typeof(a) # => Int32 | String
유니언은 임의로 많은 수의 타입으로 구성될 수 있어요. 타입이 유니언 타입인 표현식에 메서드를 호출하면, 유니언에 속한 모든 타입이 그 메서드에 응답해야 해요. 그렇지 않으면 컴파일 타임 오류가 나요. 그 메서드 호출의 타입은 각 메서드 반환 타입의 유니언 타입이 돼요.
# to_s is defined for Int32 and String, it returns String
a.to_s # => String
a + 1 # Error, because String#+(Int32) isn't defined
필요하다면 변수를 컴파일 타임에 유니언 타입으로 정의할 수도 있어요.
# set the compile-time type
a = 0.as(Int32 | Nil | String)
typeof(a) # => Int32 | Nil | String
유니언 타입 규칙
일반적인 경우 두 타입 T1과 T2가 결합되면 결과는 유니언 T1 | T2예요. 하지만 결과 타입이 다른 타입이 되는 몇 가지 경우가 있어요.
같은 계층 아래의 클래스·구조체 유니언
T1과 T2가 같은 계층 아래 있고, 가장 가까운 공통 조상 Parent가 Reference, Struct, Int, Float도 Value도 아니라면, 결과 타입은 Parent+예요. 이것을 가상 타입(virtual type)이라고 하는데, 기본적으로 컴파일러가 그 타입을 Parent 또는 그 하위 타입 중 하나로 보게 된다는 뜻이에요.
예를 들어:
class Foo
end
class Bar < Foo
end
class Baz < Foo
end
bar = Bar.new
baz = Baz.new
# Here foo's type will be Bar | Baz,
# but because both Bar and Baz inherit from Foo,
# the resulting type is Foo+
foo = rand < 0.5 ? bar : baz
typeof(foo) # => Foo+
같은 크기의 튜플 유니언
같은 크기의 두 튜플의 유니언은, 각 위치의 타입들의 유니언을 가진 튜플 타입이 돼요.
예를 들어:
t1 = {1, "hi"} # Tuple(Int32, String)
t2 = {true, nil} # Tuple(Bool, Nil)
t3 = rand < 0.5 ? t1 : t2
typeof(t3) # Tuple(Int32 | Bool, String | Nil)
같은 키를 가진 네임드 튜플 유니언
같은 키를 가진(순서는 무관) 두 네임드 튜플의 유니언은, 각 키의 타입들의 유니언을 가진 네임드 튜플 타입이 돼요. 키의 순서는 왼쪽 튜플의 순서를 따라요.
예를 들어:
t1 = {x: 1, y: "hi"} # 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)
더 알아보기
- 유니언 타입에 메서드를 호출하면 그 메서드는 유니언의 모든 타입에서 정의되어 있어야 해요. 그렇지 않으면 컴파일 오류예요.
- 같은 계층 아래의 타입을 유니언하면
Foo+같은 가상 타입이 되어,Parent또는 하위 타입 중 하나로 간주돼요. - 튜플·네임드 튜플의 유니언은 위치별·키별로 타입 유니언을 만든다는 점을 기억하면 좋아요.