상수와 상수 표현식

상수와 상수 표현식

Nim에서 상수(constant)는 상수 표현식(constant expression)의 값에 묶인 기호예요. 이번 섹션에서는 어떤 값과 연산이 상수 표현식이 될 수 있는지, 그리고 컴파일 타임 변수를 다루는 Nim 특유의 유연함을 같이 살펴볼게요.

출처: Nim Manual

본문

상수 표현식은 다음 범주의 값과 연산에만 의존하도록 제한돼요. 이 항목들은 언어에 내장되어 있거나, 상수 표현식의 의미 분석(시맨틱 분석)이 시작되기 전에 이미 선언되고 평가된 것들이기 때문이죠.

  • 리터럴(literal)
  • 내장 연산자(built-in operator)
  • 미리 선언된 상수와 컴파일 타임 변수(compile-time variable)
  • 미리 선언된 매크로(macro)와 템플릿(template)
  • 컴파일 타임 변수를 수정하는 것 외에는 부수 효과가 없는, 미리 선언된 프로시저(procedure)

상수 표현식 안에는 코드 블록을 둘 수 있고, 그 블록 내부에서는 컴파일 타임에 지원되는 모든 Nim 기능을 쓸 수 있어요. 자세한 내용은 바로 다음 섹션에서 다루죠. 이런 블록 안에서는 변수를 선언했다가 나중에 읽거나 갱신할 수도 있고, 변수를 선언해서 그걸 수정하는 프로시저에 넘겨주는 일도 가능해요. 다만 그 블록 안에서 블록 밖의 값과 연산을 참조하는 부분은, 위에서 나열한 제약을 그대로 지켜야 해요.

컴파일 타임 변수를 읽고 수정할 수 있다는 점이, 다른 정적 타입 언어에서 넘어온 분들에겐 상수 표현식의 유연함으로 다가올 수 있어요. 아래 코드는 피보나치 수열의 앞부분을 컴파일 타임에 출력해요. 다만 이건 상수를 정의하는 유연성을 보여주는 데모일 뿐, 이 문제를 푸는 권장 스타일은 아니에요.

import std/strformat

var fibN {.compileTime.}: int
var fibPrev {.compileTime.}: int
var fibPrevPrev {.compileTime.}: int

proc nextFib(): int =
  result = if fibN < 2:
    fibN
  else:
    fibPrevPrev + fibPrev
  inc(fibN)
  fibPrevPrev = fibPrev
  fibPrev = result

const f0 = nextFib()
const f1 = nextFib()

const displayFib = block:
  const f2 = nextFib()
  var result = fmt"Fibonacci sequence: {f0}, {f1}, {f2}"
  for i in 3..12:
    add(result, fmt", {nextFib()}")
  result

static:
  echo displayFib

코드 흐름을 짚어볼게요. fibN, fibPrev, fibPrevPrev는 전역으로 선언된 컴파일 타임 변수라서, nextFib()가 호출될 때마다 그 함수 밖에도 그 상태가 남아요. 그래서 const f0 = nextFib(), const f1 = nextFib()처럼 const로 호출할 때마다 한 항씩 다음 피보나치 수를 뽑아낼 수 있죠. 마지막의 static: 블록은 이 값을 컴파일 타임에 출력해요.

더 알아보기 (Learn more)