지금까지 실러버스에서는 타입에 대해 많이 다루지 않았지만, Julia에는 타입이 있다는 것은 분명해요:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
우리가 타입을 지정한 적은 없지만, Julia는 알아서 타입을 할당했어요.
types가 있고, 이는 설계의 핵심이에요.Type Inference를 이용해 타입을 "추측"할 수 있어요.JIT 컴파일러는 코드 (전체)를 훑어보고 변수가 어떻게 사용되는지 확인한 다음, 그 사용법에 맞는 적절한 기본 타입을 _추론_해요.
간단한 튜토리얼 연습 문제를 푸는 데에는 타입 추론에 의존해도 괜찮지만, 더 큰 프로그램에서는 더 정밀한 제어가 필요할 가능성이 커요.
대부분의 최신 프로세서에서 정수는 기본적으로 Int64가 돼요.
Numbers 개념에서 값을 기본값이 아닌 특정 타입으로 변환할 수 있다는 것을 봤어요.
julia> x = Int16(42)
42
julia> typeof(x)
Int16
하지만 변수 x에는 여전히 다른 타입을 다시 할당할 수 있어요:
julia> x = "changed"
"changed"
julia> typeof(x)
String
이것이 type instability인데, 이는 다음과 같아요:
대신 :: 연산자를 사용해 x의 타입을 지정할 수 있어요:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
이제 y는 Int16 타입이고, 앞으로도 항상 그럴 거예요.
따라서 컴파일러는 이 변수를 위해 몇 바이트를 예약해야 할지 알 수 있고, 나머지 코드가 안정적인 타입에 의존하도록 최적화할 수 있어요.
이 점에서 이 변수는 C 같은 정적 타입 언어의 변수와 거의 비슷해요.
Int64, Int16, String, Char: 이 타입들은 과연 어디에서 "왔을까요".
많은 객체 지향(OO) 언어에서 각 타입은 클래스이고, 하위 클래스화를 통해 이들이 클래스 계층 구조로 정리되며, 클래스 메서드가 동작을 정의해요.
Java와 Ruby가 이런 패턴의 확실한 예이고, Python도 내부적으로는 비슷해요.
Julia에는 클래스가 없어요.
문서에 나온 이유는 객체 지향 기능이 JIT 컴파일러를 방해하고 런타임 성능을 떨어뜨리기 때문이에요.
그런데도 이 코드를 한번 봐요:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
몇 가지 세부 사항을 채워 보면:
Int16은 타입이고, 이 타입의 변수를 만들 수 있어요.Int16은 Signed의 하위 타입이고, supertype() 함수가 이를 보여줘요.Integer, Real, Number를 거쳐 맨 위의 Any에 이르는 타입 계층 구조가 있고, subtypes()가 이 계층 구조의 해당 가지를 보여줘요.모든 가지는 Any에서 끝나는데, Any는 자기 자신이 자신의 상위 타입이라는 점에서 특별해요.
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
즉, Julia에는 class 계층 구조가 없지만, type 계층 구조는 있어요.
결국에는 이게 어떻게 동작하는지 하나씩 풀어볼 거지만, 그 전에 살펴볼 것이 훨씬 많아요.
typeof()를 사용해 평소처럼 값이 같은지 검사할 수 있어요.
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
이런 비교 방식은 타입 계층 구조를 전혀 고려하지 않기 때문에, 타입이 정확히 같아야 해요.
더 유연하게는, isa가 값의 타입이 비교 대상과 같거나 그 하위 타입인지 알려줘요.
isa는 함수 형태로도, 중위 연산자 형태로도 사용할 수 있어요.
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
isa는 왼쪽에 type이 아니라 value가 온다는 점에 유의해요.
이런 방식으로 두 _타입_을 비교하려고 하면 예상치 못한 결과가 나와요.
올바른 연산자는 <:이고, 앞으로 나올 개념에서 훨씬 더 자주 보게 될 거예요.
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
타입 계층 구조가 (컴퓨터 과학에서 말하는 것처럼, 뿌리가 위에 있는) 트리 구조를 이룬다는 것을 봤어요.
트리의 각 항목은 node이고, 이들은 다음과 같이 분류할 수 있어요:
abstract라고 해요.concrete라고 해요.julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
구체 타입만 변수로 instantiated될 수 있기 때문에, 이 구분은 중요해요.
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
추상 타입을 사용하려고 해도 (이 경우에는) 오류 메시지가 나오지 않지만, 컴파일러가 Integer 대신 Int64처럼 적절한 구체 타입을 만들어 준다는 점에 유의해요.
이 연습 문제에서는 채점 데이터를 전처리하면서 간단히 데이터 엔지니어링을 해볼 거예요. 학생이 아주 아주 많은 (어쩌면 너무 많은?) 학교에서 학생들이 받은 성적을 데이터 분석하려고 하는데, 이걸 효율적으로 하고 싶어 해요. 그래서 데이터를 최소한으로 줄이고 정리하는 것이 목표예요.
채점 척도는 보통 0에서 10까지이므로, 성적 하나당 8비트만 쓰는 데이터 타입을 사용하는 것이 가장 효율적이에요.
UInt8 데이터 타입으로 변환해야 해요.Int8 데이터 타입으로 변환해야 해요.성적을 변환하는 함수를 구현하고 나면, 성적이 담겨 있는 컬렉션을 처리할 함수를 하나 더 작성해야 해요. 이 함수는 성적을 demote한 뒤 내림차순으로 반환해요.
Vector에 저장해요.Set에 성적을 저장해요.두 함수 모두 잘못된 입력이 들어오면 MethodError를 던져서 처리해야 해요.
예외 처리는 이후 개념에서 다룰 예정이니, 이 연습 문제에서는 다음 문법을 사용하면 돼요:
throw(MethodError(f, args))
여기서 f는 함수이고, args는 함수에 입력된 인자들의 튜플이에요.
demote(n) 함수의 경우, f는 demote이고 args는 (n,)예요:
throw(MethodError(demote, (n,)))
demote(n) 메서드를 구현해요.
Float64 입력이 들어오면 가장 가까운 정수로 올림한 뒤 UInt8 데이터 타입으로 반환해요.Integer가 들어오면 같은 정수를 Int8 데이터 타입으로 반환해요.julia> demote(4.2)::UInt8
5
julia> demote(4)::Int8
4
julia> demote("hi")
MethodError: no method matching demote(::String) #output truncated
preprocess(coll) 메서드를 구현해요.
Vector가 입력되면 모든 숫자를 demote하고 벡터를 뒤집어요.Set이 입력되면 모든 숫자를 demote하고 내림차순으로 정렬된 벡터를 반환해요.julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
3
2
1
julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
6
4
3
julia> preprocess(42)
MethodError: no method matching preprocess(::Int64) #output truncated
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.