openlang

CTFE는 선언이 요구하는 곳에서만 일어난다

D의 CTFE를 한동안 잘못 그리고 있었어요. 함수가 컴파일 타임 평가에 적합하면, 인자를 아는 곳 어디서든 컴파일러가 알아서 평가해 줄 거라고 생각했거든요. 실제로는 컴파일 타임 값이 꼭 필요한 곳에서만 해요.

import std.stdio;

int sumSquares(int n)
{
    if (!__ctfe)
        writeln("sumSquares ran at run time");
    int total = 0;
    foreach (i; 0 .. n)
        total += i * i;
    return total;
}

enum a = sumSquares(4);       // interpreted by the compiler
immutable b = sumSquares(4);  // module scope, also compile time

void main()
{
    auto c = sumSquares(4);   // ordinary call, prints the line
    writeln(a, " ", b, " ", c);
}

이 코드는 c에서 런타임 줄을 한 번 찍고 그다음 14 14 14를 출력해요. enum과 모듈 수준 immutable은 둘 다 컴파일 중에 값이 있어야 하니까 거기서 sumSquares가 해석되고 __ctfe가 참이에요. main 안의 auto는 그냥 평범한 호출이에요. 같은 함수가 같은 인자로 방금 두 번이나 해석됐는데도요.

이제 정리하다가 그 auto를 enum으로 바꾼다고 해 봐요. 호출부에서 바뀌는 건 키워드 하나뿐인데, 함수에는 인터프리터가 전부 실행할 수 있어야 한다는 새 요구 조건이 조용히 붙어요. 읽는 사람이 받는 표시가 정말 선언 키워드 하나뿐인가요?

영어에서 번역원문 보기

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    그러니까 요청하는 단어는 함수가 아니라 변수 쪽에 붙는 거네요. 그럼 main 안의 immutable d = sumSquares(4);도 CTFE를 거치겠네요. 절대 바뀌지 않을 값이면 기다릴 이유가 없으니 컴파일러가 채워 넣는 게 자연스럽잖아요.

    영어에서 번역원문 보기
  2. 고유 링크

    그렇지 않다. immutable은 값이 바뀌지 않는다는 뜻이다. 언제 계산되는지는 말하지 않는다. 지역 immutable은 main이 실행될 때 초기화되고, 런타임 줄이 두 번 찍힌다. 함수 안에서는 enum이나 static immutable을 쓴다. 정적 지역 변수는 컴파일 타임 초기화식이 필요하다. 그래서 CTFE를 거친다.

    영어에서 번역원문 보기
  3. 고유 링크

    요구 조건이 생기는 걸 눈으로 보는 재밌는 방법이 있어요. sumSquares에 core.stdc.stdlib의 rand() % 2를 더하게 하고 다시 컴파일해 봐요. enum과 모듈 수준 immutable이 둘 다 실패해요. 인터프리터는 소스가 없는 C 함수를 실행할 수 없거든요. main의 auto 줄은 그대로 컴파일되고 그냥 호출해요.

    그러니까 질문에 답하자면, 네, 키워드가 표시예요. 반대로도 쓸 수 있어요. enum은 함수가 아직 해석 가능한지 싸게 확인하는 수단이고, 누가 그걸 깨뜨리는 순간 컴파일러가 알려 줘요.

    영어에서 번역원문 보기
  4. 고유 링크

    고마워요, 덕분에 이해가 바로잡혔어요. 실제 경우는 조회 테이블이었는데, enum 대신 static immutable로 넣었어요. 배열 타입의 enum은 이름이 나오는 곳마다 리터럴로 붙여 넣어져서 쓸 때마다 런타임에 새 배열을 만들어요. static immutable 테이블은 바이너리 안에 한 번만 있어요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. 이 CPS 합계는 왜 Debug에서만 스택이 넘칠까요?

    제가 이해한 게 맞는지 봐 주실 수 있을까요? 연습 삼아 짠 continuation-passing 방식 합계예요. dotnet run -c Release는 1000000을 출력해요. 그냥 dotnet run, 즉 Debug는…

    답글 5fsharp
  2. 같은 Int, 두 타깃, 두 개의 답

    같은 Gleam 모듈을 두 타깃에서 돌리다가 발견했는데, 언어보다는 런타임 쪽 사실이에요. gleam run은 9007199254740993을 출력해요. gleam run --target javascript는 90071992…

    답글 3gleam
  3. expr를 감싸는 중괄호가 바이트코드 컴파일러가 보는 것을 정한다

    중괄호 없는 expr는 할 수만 있다면 Tcl에서 금지하고 싶은 습관이에요! 스타일 지적처럼 보이지만 전혀 아니에요. 중괄호 버전은 0을 찍어요. 바이트코드 컴파일러가 $qty == 3을 식으로 보고, 변수를 한 번 읽고,…

    답글 16tcl