6P by GN⁺ | ★ favorite | 댓글 1개
  • 생산자와 소비자 코드가 서로 데이터를 주고받을 때 한쪽을 피호출자 형태로 뒤집어 쓰면, 원래 보이던 알고리듬 구조가 상태 전이에 묻히기 쉬움
  • Knuth식 코루틴은 두 루틴이 실행 위치를 저장하며 제어를 주고받는 모델이지만, C의 스택 기반 호출 구조에서는 이식 가능한 방식으로 직접 구현하기 어려움
  • 이 글의 핵심 트릭은 switch 하위 블록에 case를 둘 수 있는 C 문법과 __LINE__ 매크로를 이용해, return 이후 위치로 재진입하는 암묵적 상태 기계를 만드는 것임
  • crBegin, crReturn, crFinish 매크로로 압축 해제기와 파서의 원래 루프 구조를 유지할 수 있지만, 보존할 지역 변수는 static이어야 하고 crReturn을 명시적 switch 안이나 같은 줄에 두면 안 됨
  • 실제 코드에서는 재진입성과 멀티스레드 제약 때문에 컨텍스트 구조체를 넘기는 개선형이 필요하며, coroutine.h는 단순 scr 매크로와 재진입 가능한 ccr 매크로를 함께 제공함

생산자와 소비자를 연결할 때 생기는 구조 문제

  • 큰 프로그램에서는 한 코드가 데이터를 만들고 다른 코드가 소비하는 경우가 많으며, 이때 어느 쪽이 호출자가 되고 어느 쪽이 피호출자가 될지가 설계를 어렵게 만듦
  • 예시는 두 개의 작은 루틴으로 구성됨
    • 실행 길이 압축 해제 코드는 getchar()로 입력을 읽고 emit()으로 문자를 하나씩 출력함
    • 파서 코드는 getchar()로 문자를 읽어 알파벳 연속 구간은 WORD, 그 외 문자는 PUNCT로 처리함
  • 두 루틴은 따로 보면 자연스럽지만, 압축 해제기의 emit() 출력이 파서의 getchar() 입력으로 바로 이어지려면 둘 사이를 연결할 구조가 필요함
  • 두 프로세스나 두 스레드 사이의 파이프로도 풀 수 있음
    • 압축 해제기의 emit()은 파이프에 쓰고, 파서의 getchar()는 반대편에서 읽음
    • 이 방식은 단순하고 견고하지만 무겁고 이식성이 낮아, 단순한 작업에 스레드를 나누고 싶지 않은 경우가 많음

함수 재작성으로 생기는 가독성 손실

  • 전통적인 해법은 통신 채널의 한쪽 끝을 호출 가능한 함수 형태로 재작성하는 것임
  • 압축 해제기를 매 호출마다 문자 하나를 반환하는 함수로 바꾸면, 기존 파서는 getchar() 대신 decompressor()를 호출할 수 있음
  • 반대로 파서를 문자 하나를 받을 때마다 호출되는 함수로 바꾸면, 기존 압축 해제 코드는 emit() 대신 parser()를 호출하면 됨
  • 둘 다 바꿀 필요는 없고 한쪽만 바꿔도 연결은 가능하지만, 재작성된 코드는 원본보다 훨씬 읽기 어려워짐
    • 원래 압축 해제기와 파서는 알고리듬 흐름이 루프 안에 자연스럽게 드러남
    • 재작성된 형태는 static 상태 변수와 switch 상태 전이에 의존해, 압축 형식이나 파서 문법을 코드에서 읽어내기 어려움
  • 목표는 어느 쪽도 명시적 상태 기계처럼 뒤집어 쓰지 않고 연결하는 것임

Knuth식 코루틴과 C의 한계

  • Donald Knuth의 코루틴 해법은 호출자와 피호출자 구분을 버리고, 두 프로세스를 협력하는 동등한 존재로 다룸
  • 이 모델의 호출 원리는 일반 함수 호출과 다름
    • 현재 실행 위치를 스택이 아닌 별도 위치에 저장함
    • 다른 루틴이 마지막으로 저장해 둔 실행 위치로 점프함
    • 압축 해제기가 문자를 방출할 때는 자신의 프로그램 카운터를 저장하고 파서의 저장 위치로 이동함
    • 파서가 다음 문자를 필요로 할 때는 자신의 프로그램 카운터를 저장하고 압축 해제기의 저장 위치로 이동함
  • 제어는 두 루틴 사이를 필요한 만큼 왕복함
  • 이 방식은 이론적으로 좋지만, 실제로는 어셈블리어에서만 가능함
  • C 같은 고수준 언어는 스택 기반 구조에 의존하므로, 함수 간 제어 이동에서는 한쪽이 호출자이고 다른 한쪽이 피호출자여야 함
  • 이식 가능한 C 코드에서 순수 코루틴 방식은 Unix 파이프 해법만큼 실용성이 떨어짐

C에서 흉내 내는 “return and continue”

  • C에서 필요한 동작은 피호출자 함수가 return한 뒤, 다음 호출 때 그 return 바로 다음 위치에서 이어 실행되는 return and continue
  • 예를 들어 for (i = 0; i < 10; i++) return i; 형태의 함수가 10번 호출될 때 0부터 9까지 순서대로 반환하면 이상적임
  • 첫 구현은 상태 변수와 goto를 사용함
    • 함수 시작과 각 return 뒤에 라벨을 둠
    • 호출 간 유지되는 state 변수가 다음 재개 라벨을 가리킴
    • 함수 시작 시 switch(state)로 적절한 라벨로 이동함
    • return 직전에는 다음 호출 때 돌아올 라벨을 state에 저장함
  • 이 방식은 동작하지만 라벨 관리가 수동이라 유지보수 부담이 큼
    • return을 추가할 때마다 새 라벨을 만들고 초기 switch에도 추가해야 함
    • return을 제거할 때는 대응 라벨도 제거해야 함
    • 함수 본문과 switch 목록의 일관성을 계속 맞춰야 함

Duff’s device로 숨긴 상태 기계

  • C의 유명한 Duff’s deviceswitch에 대응하는 case 문을 그 하위 블록 안에도 둘 수 있다는 문법을 활용함
  • 이 성질을 코루틴 트릭에 적용하면, switch가 어떤 goto를 실행할지 고르는 대신 switch 자체가 재진입 점프처럼 작동함
  • 기본 형태는 다음과 같음
    • static int state가 다음 재개 지점을 저장함
    • 함수 시작에서 switch(state) { case 0: ... }로 진입함
    • return 직전 state에 다음 case 값을 저장함
    • return 바로 뒤에 해당 case 라벨을 둠
  • 이를 매크로로 감싸면 코루틴처럼 보이는 인터페이스가 됨
    • crBegin: static int state=0; switch(state) { case 0:를 숨김
    • crReturn: state를 저장하고 값을 반환한 뒤, 같은 위치에 case 라벨을 배치함
    • crFinish: 열린 블록을 닫음
  • crReturndo ... while(0)로 감싸져 있어 ifelse 사이에서 중괄호 없이 써도 문법 문제가 생기지 않음
  • 처음에는 crReturn(1, i)처럼 상태 번호를 직접 줘야 하지만, ANSI C의 __LINE__ 매크로를 쓰면 현재 소스 줄 번호를 상태 값으로 사용할 수 있음
  • 이 개선 뒤에는 crReturn(x)만 쓰면 되지만, 한 줄에 crReturn을 두 개 두면 안 된다는 규칙이 추가됨

매크로 사용 규칙과 예시

  • 매크로 기반 코루틴은 몇 가지 규칙을 전제로 함
    • 함수 본문을 crBegincrFinish로 감쌈
    • crReturn을 넘어서 보존되어야 하는 지역 변수는 static으로 선언함
    • 명시적인 switch 문 안에는 절대 crReturn을 넣지 않음
    • __LINE__ 기반 구현에서는 같은 줄에 crReturn을 두 개 넣지 않음
  • 압축 해제기 예시는 원래 루프 구조를 유지한 채, 문자를 방출할 때 emit(c) 대신 crReturn(c)를 사용함
  • 파서 예시는 새 문자가 필요할 때 crReturn()으로 호출자에게 돌아가고, 다음 호출에서 매개변수 c에 새 문자를 받은 상태로 이어 실행함
  • 파서에는 작은 구조 변경이 있음
    • 첫 문자가 함수 진입 시 이미 c에 들어 있으므로, 원래 루프 시작 부분의 getchar()에 해당하는 crReturn이 루프 끝으로 이동함
    • 원한다면 파서에 초기화 호출이 필요하다고 정할 수도 있음
  • 두 루틴을 모두 코루틴 매크로로 바꿀 필요는 없으며, 한쪽만 바꾸고 다른 한쪽은 호출자로 남겨도 됨
  • 결과적으로 ANSI C와 전처리기, switch의 덜 쓰이는 문법을 결합해 생산자와 소비자 사이의 데이터 전달을 명시적 상태 기계 재작성 없이 처리함

코딩 표준과 알고리듬 명확성의 충돌

  • 이 기법은 일반적인 코딩 표준을 심하게 위반함
    • 매크로 안에 맞지 않는 중괄호가 들어감
    • 하위 블록 안의 case를 사용함
    • crReturnswitch, return, case를 한 매크로 안에 숨김
  • 문법 구조를 숨기는 매크로는 코딩 표준상 명확성을 해친 것으로 볼 수 있음
  • 그러나 명시적 상태 기계로 재작성한 함수도 작은 case STATE 블록과 상태 전이로 구성되어, goto 라벨 블록을 나열한 함수와 시각적 구조가 크게 다르지 않음
  • 함수가 길어질수록 상태 기계 재작성은 원래 알고리듬 구조를 더 많이 훼손함
  • 이 기법은 문법적 구조를 일부 숨기는 대신 알고리듬 구조를 더 잘 드러내려는 절충임

재진입 가능한 개선형과 제공 코드

  • 단순한 장난감 구현은 static 변수에 의존하므로 재진입 가능하지 않고 멀티스레드에도 적합하지 않음
  • 실제 애플리케이션에서는 같은 함수를 여러 컨텍스트에서 호출하고, 각 컨텍스트마다 마지막 return 뒤에서 이어 실행할 수 있어야 함
  • 개선 방식은 컨텍스트 구조체 포인터를 추가 매개변수로 넘기는 것임
    • 지역 상태와 코루틴 상태 변수를 모두 구조체 멤버로 둠
    • 루프 카운터 같은 변수도 i 대신 ctx->i처럼 접근해야 함
    • 코드가 조금 더 못생겨지지만, 재진입성 문제를 제거하면서 루틴의 전체 구조는 유지함
  • C++ 사용자는 코루틴을 클래스 멤버로 만들고 지역 변수에 해당하는 상태를 클래스 안에 두어 스코프를 더 자연스럽게 처리할 수 있음
  • 제공되는 coroutine.h는 이 코루틴 트릭을 미리 정의된 매크로 세트로 구현함
    • scr 접두사의 매크로는 static 변수를 쓰는 단순형임
    • ccr 접두사의 매크로는 재진입 가능한 고급형임
    • 자세한 문서는 헤더 파일 안의 주석에 포함됨
  • Visual C++ 6은 기본 디버그 설정인 “Program Database for Edit and Continue”에서 __LINE__ 매크로를 이상하게 처리해 이 트릭을 싫어함
    • VC++ 6에서 코루틴 사용 프로그램을 컴파일하려면 Edit and Continue를 꺼야 함
    • 프로젝트 설정의 “C/C++” 탭, “General” 범주, “Debug info” 설정에서 “Program Database for Edit and Continue”가 아닌 옵션을 선택해야 함
  • 헤더 파일은 MIT 라이선스로 제공됨

관련 참고와 실제 사용

  • Donald Knuth의 The Art of Computer Programming, Volume 1, Section 1.4.2는 순수 형태의 코루틴을 다룸
  • Tom Duff의 Duff’s device 논의에는 유사한 코루틴 트릭을 독립적으로 떠올렸을 가능성을 시사하는 내용이 있으며, 2005-03-07 업데이트에서 Tom Duff가 블로그 댓글로 이를 확인
  • PuTTY의 SSH 프로토콜 코드는 이 코루틴 트릭을 실제로 사용함
  • PuTTY 사례는 심각한 프로덕션 코드에서 보기 드문 강한 C 해킹 수준임

댓글과 토론

Hacker News 의견들
  • C 프로젝트에서 API 복잡도를 줄이려다 이 페이지를 여러 번 다시 찾게 됐고, 제어 흐름 설명이 훌륭하다고 봄
    스택 안팎의 상태 저장과 접근 방식별 가독성 차이를 더 명확히 생각하는 데도 도움이 됐음
    지금 결론은 C 코루틴을 쓸지는 라이브러리 사용자가 정하는 게 낫다는 것임. 예를 들어 Mongoose(https://github.com/cesanta/mongoose)는 비동기를 이벤트 콜백으로 처리하는데, 이런 라이브러리는 신화적인 크로스플랫폼 C 코루틴이나 더 나쁜 std::thread로 포팅하려 하기보다, 각 시스템의 스레드/태스크 기본 요소로 감싸는 편이 훨씬 쾌적함

  • 코루틴은 정말 멋진 개념이고, 특히 Microsoft 쪽 사람들이 주로 발표한 CppCon의 C++ 코루틴 영상들을 보는 것도 즐거움. “음수 비용 추상화”라는 훅도 꽤 좋음
    몇 년 전 Meta 친구들이 C++ 코루틴을 쓰기 시작했는데, 결국 큰 실수였다고 말해줬음. 컴파일러 구현 버그를 마주해야 했고, 추적하기 상당히 지저분했을 것임. Google에서는 google3/에 제대로 통합하는 뛰어난 사람들이 이제 써도 된다고 알려주기를 기다리는 중임
    이 글은 Duff의 장치 [1]를 통해 매크로 기반 구조적 goto를 C 코루틴 구현 전략으로 설명함. 핵심은 switch 블록 안 거의 어디서나 case 문을 둘 수 있다는 점이고, 함수 전체를 switch로 감싼 뒤 static 변수로 마지막 코루틴 반환 위치를 저장하고 각 coReturncase로 라벨링하는 식임
    Sustrik의 C 코루틴 글도 흥미로울 수 있음 [2]
    [1] https://en.wikipedia.org/wiki/Duff%27s_device
    [2] https://250bpm.com/blog:48/index.html

    • 최근 몇 년 사이 google3에서 fbcode로 옮겨 보니, C++ 코드 곳곳에 co_yield, co_return, co_await 같은 비동기 코드가 흩어져 있는 건 장단점이 모두 있음
      google3 내부 방식과 비교한 장점은 코드를 읽을 때 각 부분의 비동기성이 분명히 보인다는 것임. Google의 일부 프로그래머는 분기 단위 이상으로 스레딩 모델을 모르고 지내다가 나중에 심각한 버그를 만들기도 했음
      단점은 더 단순함. 많은 코드가 “비동기일 수도” 있다 보니, 시간이 지나며 프로그래머가 그 모드로 작성한다는 이유만으로 전체가 비동기가 되어감. 스핀락과 양보하는 뮤텍스 중 무엇을 쓸지는 임계 구역 크기와 당시 스레딩 상황에 따라 정해야 하는데, 가독성과 일관성을 맞추다 보면 프로젝트 전체가 한쪽으로 쏠리기 쉬움
      기본값을 어느 한쪽으로 두지 않고, 이전 실행의 프로파일을 바탕으로 다음 실행을 더 최적화하되 코드 변경이나 버그 없이 해주는 스레딩 언어 구현을 더 알고 싶음
    • 대안으로 GCC의 labels as values 기능을 쓸 수 있음. 라벨의 주소를 얻어 나중에 그곳으로 점프할 수 있음. 2005년에 지금 lc-addrlabels.h에 들어간 코드를 기여했음
      GCC의 지역 라벨 기능도 써서 __LINE__ 사용을 완전히 피했고, 한 줄에 여러 coReturn을 둘 수도 있었음
    • Duff가 switch 블록 안 거의 어디서나 case 문을 쓸 수 있다는 걸 깨달았다는 말은 아마 맞겠지만, 그 기능 자체는 거의 확실히 의도된 기능
      글 하단에도 나오듯 Duff는 그 위에 코루틴을 만들 수 있다는 점도 깨달았지만, 그 생각을 “역겹다”고 봤음
      C의 switch를 표현력 낮은 패턴 매칭처럼 생각하면 “fallthrough”가 버그처럼 보이기 쉬운데, 그렇지 않음. Fortran의 것과 같은 계열의 계산된 goto이고, 값이 연속일 필요도 없고 라벨을 맨 위에 전부 나열할 필요도 없어 더 편함. 적어놓고 보니 계산된 COMEFROM에 더 가까운 것 같기도 함
    • 아, C 전처리기는 세월이 지나도 계속 선물을 주는구나 :-(
  • “널리 쓰이는 고수준 언어는 코루틴을 지원하지 않는다”는 말은 2000년에는 맞았을 수 있지만, 지금은 C++20, Lua, Python, Ruby 등 많은 언어가 지원함

    • Python은 1991년에 만들어졌으니 yield 키워드도 그때나 그리 오래 지나지 않아 생겼을 것 같음
      글 끝의 개선안인 “컨텍스트 구조체 포인터를 추가 함수 인자로 두고, 모든 지역 상태와 코루틴 상태 변수를 그 구조체의 원소로 선언한다”는 방식은 클로저 구현처럼 보임. 호출받는 쪽을 람다로 만들고 외부 변수/컨텍스트/상태를 써서 무엇을 할지나 어떤 값으로 할지 결정하게 하는 것 같은데, 이렇게 이해한 게 맞는지 궁금함
    • 참고로 Simula67에는 코루틴이 있었음. 최초는 아니지만, 기억하기로는 코루틴을 지원한 첫 주요 언어였음
  • switch 방식은 아주 드물지는 않지만, 보통은 초기화 함수와 코루틴 함수에 전달되는 상태 포인터를 둠
    임베디드 프로젝트에서 이 방식을 많이 썼고, 한 코루틴은 모터 가속/감속을 처리하고 다른 코루틴은 어느 방향으로 갈지만 알려주는 식이었음. 네트워크 라이브러리[1]에도 쓴 적 있음. 표준 라이브러리에도 strtok()[2] 같은 코루틴 함수가 있음
    관리 가능하게 만들려면 매크로 지옥까지 도입할 필요는 없지만, switch/case 흐름을 읽는 게 즐거웠던 적은 없음
    [1]: https://github.com/REONTeam/libmobile/blob/master/relay.c#L3...
    [2]: https://manpages.debian.org/bookworm/manpages-dev/strtok.3.e...

  • 같은 저자의 Simon Tatham's Portable Puzzle Collection도 있음
    https://www.chiark.greenend.org.uk/~sgtatham/puzzles/

  • 이게 C 흑마법처럼 보인다면, 같은 저자가 매크로로 임의의 제어 구조를 만드는 글도 읽어볼 만함: https://www.chiark.greenend.org.uk/~sgtatham/mp/

    • 밑줄 접두사 방식도 여전히 이름 가리기에 취약한 경우가 많음. 피하려면 꽤 보기 흉하게 이름을 맹글링해야 하고, 표현식/문장에 가까운 매크로와 달리 외부 블록 매크로에서는 GNU/C23의 위생적 매크로 해킹으로도 피할 수 없음
  • 코루틴은 재미있지만, 실제 코드에서는 진짜 스레드 사용도 고려해야 함. 현대 프로세서는 코어가 많지만 코루틴은 흔히 단일 코어만 사용함
    실제 문제이기도 함. 최근까지 코루틴을 많이 쓰는 qemu는 블록 장치 I/O 상당수를 단일 스레드로 보냈고, 이 때문에 성능 문제가 있었음. Kevin Wolf와 다른 사람들이 수년간 고쳐서 현대 qemu는 I/O에 여러 스레드를 쓰게 됐고, 이 작업은 RHEL 9.4에 들어갈 예정임

    • 스레드와 코루틴의 유일한 연결점은 일부 단일 스레드 언어 런타임이 코루틴만 제공해서, 가끔 스레드가 더 나은 선택인 곳에 코루틴을 쓰게 된다는 정도임
      코루틴은 단일 스레드 실행을 구조화하는 방법이고, 그 자체로 유용함. 본문 예시인 생산자-소비자 패턴이 좋은 예이며, 스트림을 파서에 붙이는 일은 병렬 알고리즘이 아니므로 스레드는 그걸 작성하는 데 쓸모가 없음
      병렬로 수행 가능한 작업에 단일 스레드 패러다임을 쓰면 당연히 비효율적이지만, 코루틴은 가난한 사람의 병렬성이 아니라 독립적으로 의미 있는 제어 구조임. 웹 서버에서 이벤트 루프가 디스패처와 함께 여러 블로킹 이벤트 사이로 코루틴을 꿰듯이 스레드와 생산적으로 결합할 수도 있고, 런타임이 코어당 스레드를 띄워 병렬화하면 스레드 간 조정은 각 작업 큐 깊이를 확인하고 덜 붐비는 쪽에 요청을 넘기는 정도로 줄어듦
    • 코루틴이 보통 단일 코어만 쓴다는 건 대체로 원하는 동작임. 서로 분리된 병렬 작업이라면 자연스럽게 서로 다른 데이터를 다룰 것임
      코루틴의 아이디어는 어떤 지역적 작업과 동기적 데이터가 있고, 그걸 호출자가 안쪽 루프를 “끌어오는” 함수형 패러다임보다, 함수가 무언가를 루프 돌며 결과를 다른 곳에 있는 추상화된 소비자에게 “밀어넣는” 뒤집힌 형태로 표현하는 편이 더 쉬울 때 쓰는 것임
    • 스레드와 코루틴을 섞는 데에는 꽤 좋은 지점이 있음. 스레드마다 코루틴 스케줄러 인스턴스를 두고, 코어마다 스레드를 하나 만드는 방식임
      그런 다음 스케줄러 사이로 코루틴을 옮기는 일은 거의 하지 않고, 다른 스케줄러의 코루틴끼리 데이터를 공유하는 일도 거의 하지 않음
      코루틴은 협력적 스케줄링을 통해 락이 전혀 필요 없는 편한 동시성 프로그래밍 스타일을 가능하게 함. 보통 스케줄링 지연은 더 커지지만, 원자 연산/락 오버헤드가 사라지고 선점형 스케줄링을 위해 타이머가 계속 실행을 끊지 않으므로 처리량은 꽤 높아질 수 있음
    • “진짜 스레드를 고려하라”는 조언은 일반적으로 좋지 않음. 예를 들어 평평하지 않은 컬렉션인 트리 노드 순회만 하고 싶다면 왜 별도 스레드를 돌려야 하는지 모르겠음
    • 코루틴은 가볍고 동기화가 아주 쉬움. 반복자나 토크나이저처럼 작은 단위의 점진적 계산에 딱 맞음. 아마 그린 스레드를 떠올린 것 같음
  • 이 접근법의 C++ 버전: https://www.codeproject.com/Tips/29524/Generators-in-C
    내 Sciter에서도 혹시 몰라 쓰고 있는데, 꽤 잘 동작하고 편리함

  • 이를 모듈식이고 안전하게 달성하는 방법은 아마 효과 처리기일 것임. Python의 yield와 비슷하지만 값을 반환할 수 있고, 함수 호출에 국한되지 않고 예외처럼 스코프가 잡힘. 익숙하지 않다면 이 글이 좋은 동기 부여가 됨
    직접 스타일로 작성된 각 함수는 제어가 다른 곳으로 가야 할 때 “효과”를 수행할 수 있음. 여기서는 c=getchar()emit(c)가 그런 경우임
    그러면 제어는 효과 처리기로 가고, 이 경우 아마 두 함수의 호출자가 다음에 무엇을 할지 정함. 압축 해제기가 문자를 내보내면 파서 코드에 그 문자를 넘겨 재개하고, 파서가 더 필요하다고 할 때까지 진행한 뒤 다시 압축 해제기를 재개하는 식임
    효과는 효율적으로 구현될 수 있고, 특히 continuation을 한 번만 호출할 수 있게 제한하면 더 그렇음. OCaml이 그런 경우임. 직접 스타일 코드와 타입/메모리 안전성을 함께 허용하며, 동시성 환경에서도 매우 유용함
    예시는 여기 있음: https://effekt-lang.org/docs/casestudies/lexer

  • “이 트릭은 당연히 모든 코딩 표준을 위반한다… 코딩 표준이 잘못됐다고 주장하겠다”는 부분에는 전혀 동의하지 않음
    코딩 표준이 이 코드를 거부하는 건 잘못이 아니고, 이 코드는 그저 귀여운 트릭일 뿐임. 대규모 소프트웨어 공학은 놀라움을 제거하고, 새벽 3시에 호출받아 디버깅하는 잠 부족한 사람에게도 읽히는 코드를 만드는 일임. 프로그래머가 항상 기본 규칙 네 가지를 기억하리라 기대할 수는 없음
    switch, return, case 같은 중요한 요소를 “난독화” 매크로 안에 숨겨 문법 구조를 흐렸지만 알고리즘 구조를 드러냈다는 주장도 납득하기 어려움. 좋은 프로그램은 문법 구조와 알고리즘 구조가 모두 명확해야 하고, 이 방식은 거기에 못 미침. Rust가 async 함수에서 암묵적 상태 기계를 만드는 방식이 여기서 모델이 되어야 한다고 봄

    • 최저 공통분모에 맞춰 끝없이 “멍청하게 낮추는” 태도가 오늘날 대부분의 소프트웨어 품질, 혹은 그 부재를 만든 원인임. 지식과 교육을 피하면 결국 대가를 치르게 됨
    • C에서 C++로 넘어가 보니, 무엇을 읽기 쉬운 코드로 보느냐, 즉 미래의 독자가 무엇을 이해해야 한다고 기대하느냐에서 두 커뮤니티 사이에 큰 차이가 있음
      C 세계에서는 삼항 조건 연산자도 너무 자극적이고 C99도 신문물 취급임. C++ 세계에서 템플릿 메타프로그래밍을 말리는 유일한 이유는 쓰는 표준에서 constexpr로 같은 일을 할 수 있기 때문임