openlang

매크로 대부분이 const와 static이 됐다

Nim은 매크로를 쓰기 쉽게 해 준다. 그게 문제다.

macro에 손대기 전에 더 싼 도구부터 본다. const는 매크로를 돌리는 바로 그 VM이 컴파일 시점에 평가한다. 그래서 평범한 proc으로 만든 조회 테이블은 그냥 const table = buildTable()이다. static: 블록은 컴파일 중에 코드를 돌리고 아무것도 생성하지 않는다. 설정 검사, 크기, 문자열 형식. 평범한 proc을 일찍 부를 뿐이다.

그다음은 템플릿. 단순 치환이고 AST를 돌아다니지 않는다. 작은 DSL을 원하는 경우는 대개 여기서 끝난다.

진짜 매크로가 필요한 건 앞의 셋으로 표현이 안 되는 문법뿐이다. 그때도 매크로는 노드 배치만 바꾼다. NimNode가 필요 없는 로직은 평범한 proc에 두고, 그건 다른 코드처럼 런타임에 테스트한다.

매크로는 작게, proc은 크게. 오류 메시지가 읽힌다.

이어지는 대화 4

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

    로직을 전개기 밖에 두는 이유는 재정의입니다. 평범한 함수 호출로 전개되는 매크로는 전개 결과가 작고, 그 함수는 호출하는 쪽을 건드리지 않고 다시 정의하고 테스트할 수 있습니다. 반대로 로직이 전개기 안에 있다면, 매크로를 쓴 모든 자리는 그 자리가 컴파일될 때 전개기가 만든 결과를 그대로 품고 있습니다. 그러면 로직을 바꾸려면 그 자리들을 찾아 다시 컴파일해야 하는데, 프로그램을 읽는 사람에게는 그 자리가 어디인지 기록이 없습니다. Common Lisp 코드가 이런 분업을 따르는 경향도 바로 그 이유 때문입니다.

  2. 고유 링크

    반만 동의해. C++에서는 템플릿이 매크로이자 제네릭 타입이라서 "작게 둬라"가 애초에 선택지가 아니야. 할 수 있는 건 일을 템플릿 밖으로 빼는 거지. 옛날 메타프로그래밍 대부분은 이제 constexpr 함수로 가. 그냥 함수고, 반복문이랑 if도 되고, 문맥이 요구하면 컴파일 시점에 평가돼. 꼭 그래야 한다면 consteval.

    타입이 뭘 할 수 있느냐로 오버로드 고르는 건 concepts가 맡았어. template <std::integral T>는 라이브러리 깊숙한 데서 나온 치환 실패 세 화면 대신, 호출한 자리에서 만족 못 한 제약에 대한 메시지로 실패해. 항상 짧진 않아. 더 짧긴 해.

  3. 고유 링크

    역사 쪽 각주 하나 달겠습니다. CoffeeScript는 어떤 의미에서 JavaScript 위에 얹힌 커다란 고정 매크로였습니다. 굵은 화살표, 클래스, 구조 분해, 기본 인자, 스플랫, 문자열 보간. 누구도 자기 설탕을 더할 수 없었고 컴파일러가 전부 들고 왔습니다. 그리고 그 대부분이 ES2015에서 언어 안으로 흡수됐는데, 의미가 미묘하게 달라진 것도 있습니다(CoffeeScript의 =>는 컴파일된 코드에서 바깥 스코프의 this를 붙잡았고, ES 화살표 함수는 자기 this 바인딩이 아예 없습니다. 밖에서 보면 같아 보여도 같은 메커니즘은 아닙니다).

    작은 매크로를 지지하는 제 논리는 반대 방향에서 나옵니다. 스스로 가치를 증명한 설탕은 결국 언어로 승격되는 경향이 있습니다. 그때 매크로가 작으면 떼어 내기도 쉽습니다.

  4. 고유 링크

    Agda에는 작게 유지할 컴파일 시점 하위 언어가 따로 없어요. 멋지게 들리지만 값을 치르기 전까지만이에요. 타입 검사가 항을 정규화하니까 컴파일 시점 평가는 그냥 평가고, 전체 함수라면 뭐든 거기서 돌 수 있어요. 대가는 전체성을 검사받는다는 거예요. 종료 검사기는 구조적으로 더 작은 인자에 대한 재귀를 원하고, n / 2로 재귀하는 멀쩡한 함수도 정초 재귀나 연료 인자로 고쳐 쓰기 전까지는 거부돼요. 솔직히 그렇게 고친 게 원래 함수보다 길어질 때도 있어요.

    Agda에도 리플렉션을 통한 매크로가 있긴 한데, 저는 거의 쓰고 싶어지지 않아요. 타입 검사기가 이미 코드를 돌려 주면 매크로가 할 일이 별로 안 남거든요.

같은 질문의 다른 갈래

  1. 핸들러가 반환하면 래퍼는 무엇을 보장하나요?

    깔끔하게 줄였네요. 그래도 그 안에 무슨 약속이 들어 있는지는 봐야겠어요. 래퍼 매크로가 파서 둘레에 핸들러를 설치하고, 파서는 필드가 빠지면 컨디션을 시그널해요. 핸들러는 메모를 남기고 정상적으로 반환해요. Common L…