사양을 테스트 스위트로 정의한다 — 「사양이란 roast를 통과하는 것이다」

Perl 6은 Apocalypse · Exegesis · Synopsis라는 3층의 설계 문서 체계를 가지고 있었다. 그리고 최종적으로, 사양의 정본을 테스트 스위트 roast로 옮겼다. 산문 사양에는 해석이 갈린다 · 구현과 괴리된다 · 준수를 검사할 수 없다는 세 가지 문제가 있다. 테스트는 그것을 푼다. 그러나 대신 놓은 것이 있다 — 산문은 전부에 대해 모호하게 말하고, 테스트는 일부에 대해 엄밀하게 말한다.

perlrakuroastspecificationtestinglanguage-designprogramming-languages

지난번, 내 언어 이야기로 이렇게 쓰고 끝냈다.

오라클 자신이 틀렸다면, 5개가 함께 틀린다.

이번에는 그 물음을, Perl 6이 어떻게 다뤘는지의 이야기다. 무엇을 근거로 「Perl 6으로서 옳다」고 말하는가.

3층의 설계 문서

3화에서, 361건의 RFC를 Larry Wall이 다시 소화한 데까지 썼다. 그 출력이 Apocalypse다. 그리고 실제로는, 문서는 세 종류 있었다.

문서 집필자 역할 비유
Apocalypse(묵시록) Larry Wall 설계 판단과 그 이유. 왜 그렇게 하는가 계시
Exegesis(주해) Damian Conway Apocalypse를 예로 해설한다 주석
Synopsis(개요) 여러 명 사양으로 쓸 수 있는 요약. 구현이 참조한다 요약

이름은 모두 성서에서 유래해, Perl 문화다운 말장난으로 되어 있다.

Apocalypse의 번호는 『Programming Perl』(낙타 책)의 장 번호에 대응한다. 「낙타 책의 제N장에 해당하는 부분을, Perl 6에서는 어떻게 할 것인가」라는 구성이다.

번호 주제
A1 The Ugly, the Bad, and the Good — 전체 방침
A2 기본 데이터 타입, 시길, 리터럴
A3 연산자
A4 제어 구조
A5 패턴 매칭 — 정규표현식의 재설계. 가장 영향이 컸던 한 편
A6 서브루틴 — 시그니처, 다중 디스패치
A12 객체

전부 쓰이지는 않았다. 번호가 건너뛰는 것은 그 때문이다.

Synopsis가 실질적인 사양이 되었다

구현자가 날마다 참조한 것은, Apocalypse가 아니라 **Synopsis(S01~S32)**였다.

이유는 분명하다. Apocalypse는 「왜」를 이야기하는 산문이고, 길다. 사양서로서 찾아보기에는 맞지 않는다. 한편 Synopsis는 사양으로 찾을 수 있는 형태로 정리되어 있었고, 게다가 계속 갱신되었다.

번호 주제
S02 어휘 · 데이터 타입 · 시길
S03 연산자(메타 연산자 포함)
S05 정규표현식과 grammar
S06 서브루틴과 다중 디스패치
S12 객체
S14 Role과 타입
S17 병행성
S32 표준 라이브러리

여기까지가 「산문 사양」의 시대다.

산문 사양이 가진 세 가지 문제

그리고 이 형태에는 고유의 문제가 있었다.

1. 해석이 갈린다

구현자마다 읽는 법이 다르면, 구현끼리 어긋난다. 5화에서 쓴 대로, 이 시기의 Perl 6에는 여러 구현이 있었다. Pugs가 있고, Parrot 위의 것이 있고, 훗날 Niecza(.NET 위)도 나타난다.

같은 Synopsis를 읽고, 다른 동작을 구현하는 일이 일어난다. 어느 쪽이 옳은지를 정하는 절차가, 산문에는 없다.

2. 구현과 괴리된다

문서의 갱신이 멈춰도, 아무도 알아차리지 못한다.

이것은 설계 문서 일반의 문제다. 문서는 실행되지 않는다. 그래서 틀려도, 낡아도, 빨개지지 않는다. 5화에서 쓴 「검증되지 않은 결정이, 검증되지 않은 채 쌓인다」와 같은 구조다.

3. 「준수하고 있다」를 검사할 수 없다

이것이 가장 실무적인 문제다.

어떤 구현이 「Perl 6을 준수한다」고 주장했을 때, 그 주장을 검증하는 절차가 없다. 산문을 읽고, 사람이 판단할 수밖에 없다.

roast — 사양을 테스트로 옮긴다

그래서 Perl 6은, 사양의 정본을 roast라는 테스트 스위트로 옮겼다.

Repository Of All Spec Tests. 기원은 5화에서 쓴 대로, Audrey Tang이 Pugs에서 시작한 「구현할 때마다 공식 테스트 스위트에 테스트를 쓴다」는 운용이다.

그리고 최종적으로, 이렇게 선언되었다.

사양이란, roast를 통과하는 것이다.

「6.c다」란 무엇을 의미하는가

8화에서 다루는 2015년의 6.c 릴리스는, roast의 특정 커밋을 동결한다는 형태로 정의되었다.

즉 「6.c 준수」란 「6.c 시점의 roast를 통과한다」는 의미다.

이것은 놀랄 만큼 구체적이다. 산문의 해석 문제가 사라진다.

  • 구현이 「6.c 준수」를 주장할 때, 그 주장은 기계적으로 검증할 수 있다
  • 새로운 판은, 새로운 roast의 동결점으로 정의된다
  • 옛 판의 동작은, 옛 roast가 보존되어 있는 한 재현할 수 있다

그리고 12화에서 다루는 「6.d가 기본값, 6.e가 책정 중」이라는 상태도, roast의 동결점이 둘 있고, 세 번째를 만들고 있다는 의미가 된다.

무엇을 얻고, 무엇을 놓았는가

얻은 것 놓은 것
준수를 기계적으로 검사할 수 있다 테스트에 쓰이지 않은 동작은 미규정인 채로 남는다
구현과 사양이 괴리되지 않는다 「왜 그런가」를 테스트에서는 읽을 수 없다
판을 동결할 수 있다(6.c / 6.d) 테스트가 암묵적으로 구현의 사정을 고정해 버릴 위험

오른쪽 열의 첫 줄이 중요하다. 여기에, 이 연재의 여덟 번째 형이 있다.

산문은 전부에 대해 모호하게 말하고, 테스트는 일부에 대해 엄밀하게 말한다.

산문 사양은, 언어의 전체에 대해 무언가를 말한다. 다만 모호하게. 테스트 사양은, 쓰인 곳에 대해서는 엄밀하게 정한다. 다만, 쓰이지 않은 곳에는 아무 말도 하지 않는다.

이것은 우열이 아니라, 침묵의 형태가 다르다는 이야기다.

  • 산문은 「여기는 모호하다」고 알 수 있는 형태로 침묵한다
  • 테스트는 「여기는 테스트가 없다」고 알아차리지 못하는 형태로 침묵한다

그래서 roast로 옮긴 뒤에도, Raku는 산문을 버리지 않았다. Synopsis는 역사 문서로 남고, 언어 문서(docs.raku.org)가 실용적인 설명을 맡고 있다. 정본이 roast로 옮겨졌다, 는 관계다.

다른 언어는 어떻게 하고 있는가

이 판단은 Perl 6만의 것이 아니다.

언어 사양의 정본
Raku roast(테스트 스위트)
ECMAScript ISO/ECMA의 산문 사양 + test262(공통 테스트)
Ruby CRuby의 구현 + ruby/spec
C / C++ ISO 규격(산문)
Go 언어 사양(산문) + 단일 참조 구현

ECMAScript가 가장 가깝다. 산문 사양과 test262 양쪽을 가지고 있고, 「준수」는 test262로 측정된다.

Raku가 특이한 것은, 오랫동안 구현이 하나밖에 없는데도, 사양을 구현으로부터 계속 분리해 왔다는 점이다. 6화에서 쓴 대로, Niecza가 멈춘 뒤 Raku의 실용 구현은 Rakudo뿐이었다.

보통, 구현이 하나라면 「구현이 사양」으로 족하다. Ruby가 그렇다(CRuby가 사실상의 사양). 그럼에도 Raku가 roast를 유지한 것은, 여러 구현이 있던 시대의 유산이며, 동시에 단일 구현이 된 뒤에야 비로소 효과가 있는 방파제이기도 하다.

그리고 이 절의 마지막에 쓰는 대로, 2026년에 그 유산이 효과를 발휘했다.

구현이 하나일 때, 구현의 버그는 「언어의 사양」으로 정착할 수 있다. roast가 있으면, 「그것은 테스트에 쓰여 있는가」를 물을 수 있다.

지난 회의 숙제로 돌아간다

6화를 이렇게 끝냈다.

오라클 자신이 틀렸다면, 5개가 함께 틀린다.

roast는 이 문제를 절반만 푼다.

풀리는 부분: 사양이 구현으로부터 독립한 곳에 있다. Rakudo가 어떻게 동작하는가와, Raku가 어떻게 동작해야 하는가가, 다른 저장소로 나뉘어 있다. 구현의 버그가 자동으로 사양이 되는 일은 없다.

풀리지 않는 부분: roast에 쓰이지 않은 것에 대해서는, 아무 말도 할 수 없다. 내 parity 장치로 말하자면, 인터프리터가 틀렸고 게다가 그 동작을 확인하는 테스트를 아무도 쓰지 않은 영역은, 여전히 무방비다.

그리고 Perl 6 자신은, 이 구멍을 다른 방법으로 메우고 있었다. 여러 구현이 있었다는 것이다. Pugs와 Parrot 계열과 Niecza가 같은 roast를 통과한다, 는 상황에서는, 한쪽에만 있는 동작은 「사양의 구멍인가, 구현의 버그인가」 중 하나임을 알 수 있다.

두 번째 구현은, 첫 번째 구현이 알아차릴 수 없는 종류의 버그를 낸다.

6화에서 「두 번째 이용자가 오지 않았다」고 썼다. 사양의 검증이라는 관점에서는, 두 번째 구현이 떠난 것에도 같은 손실이 있다.

Niecza가 멈춘 뒤, Raku는 10년 넘게 단일 구현으로 roast를 유지해 왔다. 합리적이지만, roast가 얼마나 사양을 포착하고 있는지를 검증하는 수단은 하나 줄어 있었다.

그리고 2026년, 두 번째가 돌아왔다

이 원고를 쓰고 있는 2026년, Rakudo 이외의 구현이 두 개 돌아가고 있다.

구현 언어 방식 roast(1,464 파일 중, 완전 통과)
mutsu Rust 바이트코드 VM 1,433(97.9%)
Raku++(rakupp) C++17(외부 의존 없음) 손으로 쓴 어휘 · 구문 분석 + 트리 워킹 평가기. 네이티브 바이너리로의 컴파일도 676(46%)

둘 다 roast를 분모로 삼아 자신을 측정하고 있다. 「Raku를 준수한다」가 주장이 아니라 숫자가 되는 것은, 사양을 테스트 스위트로 옮겼기 때문이다. 이 회의 주제가, 그대로 효과를 발휘하고 있다.

⚠️ 숫자는 2026년 9월 18일에 확인한 것. 두 프로젝트 모두 날마다 움직이고 있고, mutsu의 README는 「적합도는 매일 좋아지고 있다」고 명기하고 있다. 인용하려면 다시 측정할 것.

즉, 이 절의 우려는 부분적으로 해소되었다. roast가 얼마나 사양을 포착하고 있는지를 다시 물을 수단이, 10년 만에 돌아온 셈이다.

두 번째 구현은, 첫 번째 구현이 알아차릴 수 없는 종류의 버그를 낸다.

이 형이, 지금 다시 쓸 수 있는 상태에 있다.


다음 회(8화): Christmas. 2015년 12월 25일, 발표로부터 15년 후에 최초의 안정판이 나온다. 15년이란 무엇이었는가를, 여기서 한 번 정리한다.

← Back to Perl과 Raku의 계보