Tuple은 1차원 array인 Vector와 여러모로 아주 비슷해요. 하지만 몇 가지 중요한 차이가 있어요.
[ ] 대신 소괄호 ( )로 쓰지만, 의미가 분명한 경우에는 소괄호를 생략할 수 있어요.불변성 덕분에 컴파일러가 더 많은 최적화를 할 수 있고, 튜플은 벡터보다 성능이 좋아요. 그래서 값을 바꿀 필요가 없을 때는 튜플을 쓰는 게 좋아요.
julia> t = (3, 5.2, "xyz")
(3, 5.2, "xyz")
# Non-homogeneous:
julia> dump(t)
Tuple{Int64, Float64, String}
1: Int64 3
2: Float64 5.2
3: String "xyz"
julia> t[2]
5.2
# Immutable:
julia> t[2] = 7.3
ERROR: MethodError: no method matching setindex!(::Tuple{Int64, Float64, String}
Tuple() 생성자도 있는데, 이걸 쓰면 반복 가능한 컬렉션을 튜플로 바꿀 수 있어요. 문자열을 문자의 컬렉션으로 취급한다는 점이 특히 유용해요.
julia> Tuple("Julia")
('J', 'u', 'l', 'i', 'a')
어떤 튜플이든 인덱스로 원소에 접근하거나 원소를 순회할 수 있어요.
숫자로만 이루어진 튜플은 배열과 마찬가지로 sum() 같은 수학 함수에도 쓸 수 있어요.
여러 변수에 한 번에 대입하면 튜플을 각각의 원소로 풀어낼 수 있어요. 필요하지 않은 원소에는 더미 변수로 밑줄 _를 사용해요.
julia> tup = (1, "Julia", true)
(1, "Julia", true)
julia> num, name, _ = tup
(1, "Julia", true)
julia> uppercase(name), 2num
("JULIA", 2)
튜플의 모든 원소를 다른 튜플(또는 더 일반적으로 다른 컬렉션)로 복사하려면, 튜플을 풀어 주는 splat 연산자 ...를 쓰면 돼요. 이 내용은 함수 개념에서 더 자세히 다뤄요.
julia> new_tup = (tup..., π, '😊')
(1, "Julia", true, π, '😊')
Tuple은 값만 담아요. NamedTuple은 각 값에 이름을 짝지어 줘요.
그러면 각 필드에 "점(dot)" 표기법으로 접근할 수 있어요.
julia> nt = (a = 1, b = 7.3)
(a = 1, b = 7.3)
julia> dump(nt)
@NamedTuple{a::Int64, b::Float64}
a: Int64 1
b: Float64 7.3
julia> nt.b
7.3
이름은 내부적으로 Symbol로 저장돼요(불변 타입이고, 이름 앞에 콜론 :이 붙어요). 그래서 다른 문법으로도 접근할 수 있어요.
julia> nt[:b]
7.3
이름 있는 튜플을 만들 때 두 단계로 나누면 편리할 때가 있어요.
# Step 1
julia> Student = NamedTuple{(:name, :grade), Tuple{String, Int}}
@NamedTuple{name::String, grade::Int64}
julia> typeof(Student)
DataType
# Step 2
julia> s = Student( ("Helen", 3) )
(name = "Helen", grade = 3)
julia> dump(s)
@NamedTuple{name::String, grade::Int64}
name: String "Helen"
grade: Int64 3
이름 있는 튜플을 만드는 방법은 의외로 다양하니, 관심 있다면 문서를 확인해 봐요.
이름("키"라고도 해요)과 값을 각각 따로 얻는 것도 간단해요.
julia> keys(s)
(:name, :grade)
julia> values(s)
("Helen", 3)
이름 있는 튜플을 순회하면 _값_만 나오고 _이름_은 무시된다는 점에 주의해요.
순회할 때 이름도 필요하다면 단계가 좀 더 필요해요. NamedTuple을 Pairs로 이루어진 Vector로 변환할 수 있어요(Pair는 다른 개념에서 다뤄요).
julia> pairs(s)
pairs(::NamedTuple) with 2 entries:
:name => "Helen"
:grade => 3
julia> collect(pairs(s))
2-element Vector{Pair{Symbol, Any}}:
:name => "Helen"
:grade => 3
이렇게 보면 이름 있는 튜플은 딕셔너리와 꽤 비슷해요. 하지만 Julia의 [Dict][dict]와는 몇 가지 중요한 점에서 달라요.
Dict는 가변이지만, NamedTuple은 다른 튜플과 마찬가지로 불변이에요.Dict는 키로 받는 타입이 자유롭지만, NamedTuple은 심볼만 키로 쓸 수 있어요.튜플은(이름이 있든 없든) 함수로 구조화된 데이터를 주고받는 아주 흔한 방법이에요. 이런 경우 불변성이 좋은데, 데이터 안전성을 보장하고 컴파일러가 더 적극적으로 최적화할 수 있게 해 주기 때문이에요.