특수 타입
특수 타입 (Special Types)
Nim의 타입 시스템에는 값을 담는 평범한 타입 말고도 조금 특별한 친구들이 몇 개 있어요. 크게 세 가지를 기억하면 되는데, 컴파일 타임 상수만 받는 static[T], 타입 자체를 값처럼 다루는 typedesc[T], 그리고 표현식의 타입을 그 자리에서 알아내는 typeof예요. 이들은 제네릭과 매크로를 쓰다 보면 자연스럽게 마주치게 되어요.
본문
static[T]
이름이 말해 주듯 static 파라미터는 항상 **상수 표현식(constant expression)**이어야 해요:
proc precompiledRegex(pattern: static string): RegEx =
var res {.global.} = re(pattern)
return res
precompiledRegex("/d+") # Replaces the call with a precompiled
# regex, stored in a global variable
precompiledRegex(paramStr(1)) # Error, command-line options
# are not constant expressions
정규식이 정확히 한 번만 컴파일돼 전역 변수에 저장되는 걸 눈여겨보세요. paramStr(1)처럼 호출 시점에만 알 수 있는 값은 상수가 아니므로 컴파일 오류가 나요.
코드 생성 관점에서 보면 모든 static 파라미터는 제네릭 파라미터로 취급돼요. 즉 프로시저는 공급되는 각각의 고유한 값(또는 값의 조합)마다 따로 컴파일돼요.
static 파라미터는 제네릭 타입의 시그니처에도 등장할 수 있어요:
type
Matrix[M,N: static int; T: Number] = array[0..(M*N - 1), T]
# Note how `Number` is just a type constraint here, while
# `static int` requires us to supply an int value
AffineTransform2D[T] = Matrix[3, 3, T]
AffineTransform3D[T] = Matrix[4, 4, T]
var m1: AffineTransform3D[float] # OK
var m2: AffineTransform2D[string] # Error, `string` is not a `Number`
Number는 단지 타입 제약이고, static int는 정수 값을 요구한다는 차이를 코드 주석이 잘 짚어 주네요.
static T는 사실 밑바탕의 제네릭 타입 static[T]를 쓰기 편하게 만든 문법 설탕(syntactic sugar)이에요. 타입 파라미터를 생략하면 "모든 상수 표현식"이라는 타입 클래스를 얻고, static을 다른 타입 클래스로 인스턴스화하면 더 구체적인 타입 클래스를 만들 수 있어요.
표현식을 대응하는 static 타입으로 강제(코어스)하면 컴파일 타임에 상수로 평가되도록 만들 수 있어요:
import std/math
echo static(fac(5)), " ", static[bool](16.isPowerOfTwo)
표현식을 평가하지 못하거나 타입이 어긋나면 컴파일러가 오류를 보고해요.
typedesc[T]
Nim은 많은 문맥에서 타입의 이름을 평범한 값처럼 취급해요. 이런 값들은 컴파일 단계에서만 존재하지만, 모든 값에는 타입이 있어야 하므로 typedesc가 그 값들을 위한 특별한 타입으로 간주돼요.
typedesc는 제네릭 타입처럼 동작해요. 예를 들어 심볼 int의 타입은 typedesc[int]예요. 평범한 제네릭 타입처럼 제네릭 파라미터를 생략하면 typedesc는 "모든 타입"이라는 타입 클래스를 뜻해요. 문법 편의를 위해 typedesc를 한정자(modifier)로 쓸 수도 있어요.
typedesc 파라미터를 가진 프로시저는 암시적으로 제네릭으로 간주돼요. 공급된 타입의 고유한 조합마다 인스턴스화되고, 프로시저 본문 안에서 각 파라미터의 이름은 바인딩된 구체 타입을 가리켜요:
proc new(T: typedesc): ref T =
echo "allocating ", T.name
new(result)
var n = Node.new
var tree = new(BinaryTree[int])
여러 타입 파라미터가 있으면 그것들은 서로 다른 타입에 자유롭게 바인딩돼요. bind-once(한 번만 바인딩) 동작을 강제하고 싶다면 명시적인 제네릭 파라미터를 쓰면 돼요:
proc acceptOnlyTypePairs[T, U](A, B: typedesc[T]; C, D: typedesc[U])
일단 바인딩되면 타입 파라미터는 프로시저 시그니처의 나머지 부분에도 등장할 수 있어요:
template declareVariableWithType(T: typedesc, value: T) =
var x: T = value
declareVariableWithType int, 42
타입 파라미터에 매칭될 타입의 집합을 제약해서 오버로드 해석을 더 세밀하게 조절할 수도 있어요. 실제로는 템플릿으로 타입에 속성을 붙이는 방식으로 동작하는데, 제약은 구체 타입이거나 타입 클래스일 수 있어요:
template maxval(T: typedesc[int]): int = high(int)
template maxval(T: typedesc[float]): float = Inf
var i = int.maxval
var f = float.maxval
when false:
var s = string.maxval # error, maxval is not implemented for string
template isNumber(t: typedesc[object]): string = "Don't think so."
template isNumber(t: typedesc[SomeInteger]): string = "Yes!"
template isNumber(t: typedesc[SomeFloat]): string = "Maybe, could be NaN."
echo "is int a number? ", isNumber(int)
echo "is float a number? ", isNumber(float)
echo "is RootObj a number? ", isNumber(RootObj)
typedesc를 매크로에 넘기는 것도 거의 동일하지만, 한 가지 차이가 있어요. 매크로는 제네릭으로 인스턴스화되지 않아요. 타입 표현식이 다른 것들과 마찬가지로 NimNode로 그대로 매크로에 전달돼요:
import std/macros
macro forwardType(arg: typedesc): typedesc =
# `arg` is of type `NimNode`
let tmp: NimNode = arg
result = tmp
var tmp: forwardType(int)
typeof 연산자
참고: typeof(x)는 역사적인 이유로 type(x)라고 쓸 수도 있지만, type(x)는 권장되지 않아요.
표현식에서 typeof 값을 만들어 그 표현식의 타입을 얻을 수 있어요. 다른 많은 언어에서는 이걸 typeof 연산자라고 부르죠:
var x = 0
var y: typeof(x) # y has type int
typeof로 프로시저/이터레이터/컨버터 호출 c(X)(X는 비어 있을 수도 있는 인자 목록)의 결과 타입을 판별할 때는, c가 이터레이터인 해석이 다른 해석보다 우선돼요. 이 동작은 typeof의 두 번째 인자로 typeOfProc를 넘기면 바꿀 수 있어요:
iterator split(s: string): string = discard
proc split(s: string): seq[string] = discard
# since an iterator is the preferred interpretation, this has the type `string`:
assert typeof("a b c".split) is string
assert typeof("a b c".split, typeOfProc) is seq[string]
여기서 같은 이름 split이 이터레이터와 프로시저 둘 다로 존재해요. 기본적으로는 이터레이터 해석이 우선이라 typeof(...).split은 string이 되고, typeOfProc를 넘기면 프로시저의 결과 타입인 seq[string]을 돌려줘요.