# 027 — 계산의 핵: 상태는 값 하나, 값은 몇 가지로 짓는다 작성일: 2026-10-13. 상태: 설계와 단계. [026](026-app-contract.md)이 얼릴 계약 안에서 계산의 언어를 다시 세운다. 잴 도구는 [140](../research/140-spec-suite.md)의 명세 묶음이다. 1.0 전이라 호환은 챙기지 않는다. 사용자의 말: "진행해" (2026-10-13). 1단계를 만들었다([140](../research/140-spec-suite.md)의 뒷절). - aup로 쓴 SHA-256이 공식 벡터를 세 실행 길에서 모두 지난다. - 줄 수는 막대에 못 미쳤다(aup 52 대 파이썬 28). 남은 차이는 형식 지정, 바이트 잇기와 자르기, 값으로서의 리스트다. 이것들을 2단계에 더했다. - 시간은 C의 176배, 순수 파이썬의 0.43배다. ## 쉬운 말로 - **지금의 꼴.** aup와 Pure는 패턴마다 종류와 명령을 둔다. 커서, 인덱스, 문서, 표 보기, 메시지의 행이 각각 따로다. 그래서 세상의 명세를 담지 못하고(140), 새 패턴마다 형식과 세 실행 길이 함께 자란다. - **이 설계의 꼴.** 그것을 "값 몇 가지와, 그 값들이 서로를 담는 법"으로 바꾼다. - 상태는 타입 있는 값 하나다. - 값은 수(정수, 실수, bool), 바이트(text는 UTF-8인 바이트), 레코드, 갈래, 리스트(키가 있으면 맵)다. - **계산.** 함수(재귀 포함)와 접기다. 끝남은 문법이 아니라 예산이 보증한다. 이더리움의 가스와 같은 생각이다. 가스가 다하면 그 호출의 상태 변경이 모두 되돌아가는데, 이것이 026의 원자성과 같다. - **빠름.** 뜻이 아니라 구현에서 온다. 열로 담기, B+트리, 묶음 실행, 같은 답일 때만 차례 바꾸기가 그 수단이다. - **단계.** 다섯이다. - 1단계(바이트, 비트, 수의 리스트)로 SHA-256을 쓴다. - 2단계(중첩, 갈래, 재귀, 글자)로 JSON을 쓴다. - 3단계에서 중첩된 상태를 저장한다. - 4단계에서 패턴을 모은다. - 5단계에서 수의 모델을 정한다. - **근거.** - 140의 탐침 - 지금의 구현: 값의 칸 `Slot`, 열로 담은 B+트리 `PureList`, aup의 타입 여덟 - 선행 기술: 아래 "선행" 절 - **다음 결정.** 1단계를 만든다. ## 핵 ### 값 | 값 | 뜻 | 지금 | 바뀌는 것 | |---|---|---|---| | `bool` | 참, 거짓 | 같음 | — | | `i64` | 정확한 정수, −2^63 … 2^63−1, 넘치면 `overflow` | 같음 | 비트 연산(1단계). 이름과 리터럴의 기본은 5단계 | | `number` | 유한한 binary64 | 같음 | 5단계(수의 모델) | | `bytes` | 바이트의 열, 불변 | 길이와 text 변환뿐 | 자리로 읽기, 자르기, 잇기, 짓기(1단계) | | `text` | UTF-8로 검사한 바이트 | 같은 표현 | 글자로 읽기는 그 위의 견해(2단계) | | 레코드 | 이름 있는 필드의 곱 | 필드는 수, text, bool, i64 | 필드에 아무 값(2단계) | | 갈래 | 태그 붙은 경우의 합 | 없음 | 새로. `match`가 모든 경우를 다룬다(2단계) | | `list of T [by k]` | 차례 있는 열. `by k`면 k의 차례이고 키마다 하나다 | 레코드만, 상태와 이벤트와 메시지의 행으로 따로 | 아무 T(1단계 수, 2단계 전부). 키 없는 리스트의 키는 자리다 | | 앵커 | 편집을 따라가는 위치 | 커서와 인덱스(상태의 종류 둘) | 하나로(4단계) | ### 계산 - **그대로 두는 것.** 식, `let`, `if`, `for`(세기와 걷기), `break`. - **함수.** - 자신을 부를 수 있다(2단계). - 인라인은 구현의 선택이다. 작은 fn은 지금처럼 인라인하고, 재귀하는 fn은 부른다. - 매개변수에 타입을 적지 않는 것은 그대로다. 부르는 타입마다 한 벌을 만든다(단형화). - **`match`.** 갈래의 모든 경우를 다룬다. 빠진 경우는 컴파일 오류다(2단계). - **끝을 모르는 반복.** `while`을 둔다(2단계). 세기의 상한은 뜻에서 필요하지 않다. 묶음 실행이 시작할 때의 횟수를 쓰는 것은 구현이 상한을 알아볼 수 있을 때다. - **접기.** 뜻은 차례대로의 왼쪽 접기다. 구현은 같은 답과 같은 실패를 낼 때만 차례를 바꾼다. 묶음 실행기의 차례 없는 모으기가 이미 이 규칙을 따른다. - **비트 연산.** 정수를 무한한 2의 보수로 보고 정한다. 파이썬의 정수와 같은 뜻이다. - `&`, `|`, `^`, `~`: 비트마다의 연산이다. - `x << n`은 x·2^n이다. i64를 넘으면 `overflow`다. - `x >> n`은 floor(x / 2^n)이다. - n은 0부터 63까지다. 밖이면 `limit`이다. - 어느 기계에서나 같은 뜻이고, C의 정의되지 않은 동작이 없다. - **끝남과 결정성.** - 모든 걸음은 실행량을 쓰고, 부름의 깊이는 프레임의 바이트로 메모리 몫에 센다(2c: 프레임의 바이트는 구현과 떨어진 표다). 넘으면 `work`나 `memory`로 끝나고, 이벤트는 통째로 되돌아간다. - 결정성의 단위는 모듈이다. 같은 모듈과 같은 이벤트열이면 실패의 자리까지 같다. - 소스는 컴파일러의 판에 묶인다. EVM의 바이트코드와 가스표가 그렇다. 이것이 026의 의심 6(예산의 단위)에 대한 답이다. ### 효과 026 그대로다. 상태에 쓰기, 보내기, 답하기, `require`. ### 상태는 값 하나 - 앱의 상태는 레코드 값 하나다. 선언한 상태들이 그 필드이고, 수명(저장, transient)은 필드의 성질이다. - 저장은 바뀐 부분만 쓴다. 지금 리스트가 이미지와 로그로 하는 그대로다. 중첩된 리스트도 제 이미지와 로그를 가진다(경로로 이름을 붙인다, 3단계). - 값은 불변이라 스냅숏은 나누기만 한다. 지금 리스트의 공유 사본이 그 구현이다. ### 뜻과 표현 | 뜻 | 표현(구현이 고른다) | |---|---| | 레코드의 리스트 | 열로 담은 B+트리, 값만큼 좁힌 열, HASHED(지금) | | 수의 리스트 | 필드 하나인 레코드의 리스트(1단계), 나중에 배열 | | 바이트 짓기(`out = out + byte(x)`의 반복) | 왼쪽이 이벤트 공간의 끝에 있으면 제자리 덧붙이기 | | 표 보기 | 물을 때 칸 계산 | | 접기 | 묶음 실행, 차례 없는 모으기(같은 답일 때만) | | 작은 fn | 인라인 | ## 026의 의심에 대한 답 | 의심 | 답 | 단계 | |---|---|---| | 1. 입력의 종류 | 입력은 메시지 하나다. 키, 글자, 틱은 표면과 호스트가 만드는 메시지다 | 4 | | 2. 보기의 종류 | 보기는 아무 값을 낸다 | 4 | | 3. 커서와 인덱스 | 앵커 하나로 모은다. 다만 키가 있는 리스트에서 인덱스는 키와 같을 수 있다. 4단계에서 앵커가 text에만 필요한지 본다 | 4 | | 4. 키별 문서 | 없앤다. 키 있는 리스트가 한다 | 4 | | 5. 수의 모델 | 정할 것: 정수 리터럴의 기본, `/`와 `//`, 돈을 위한 십진수 | 5 | | 6. 예산의 단위 | 모듈로 정한다(위) | 이 문서 | | 7. 고정 한도 | 뜻에는 예산(실행량, 메모리)과 모듈의 크기만 남긴다. 상태 32, 입력 32, 레코드 16, 필드 16, 컬렉션 1, 중첩 8, aup 소스 32 KB는 뺀다 | 4 | | 8. 교체의 세부 | 수의 모델 뒤에 다시 본다 | 5 | | 9. 메시지의 행 | 메시지의 필드가 아무 값을 가지면 따로 둘 까닭이 없다 | 2 | ## 단계 ### 1단계: 바이트, 비트, 수의 리스트 (SHA-256) - **Pure.** - 바이트를 자리로 읽기(`b[i]` → i64, 밖이면 `limit`), 자르기, 잇기, 한 바이트로 짓기 - i64의 비트 연산 여섯 - 이벤트의 지역 칸이 바이트를 담는다 - **aup.** - `b[i]`, `b[a .. c]`, `b + c`, `byte(n)` - i64의 `& | ^ ~ << >>`, 16진 리터럴 `0x…` - `list of i64`와 `list of number`(안에서는 필드 하나인 레코드), 리스트 리터럴 `[ … ]` - 키 없는 리스트를 자리로 읽고 쓰기 `xs[i]` - **막대.** - aup로 쓴 SHA-256이 NIST의 짧은 메시지 벡터와 긴 메시지 벡터를 모두 지난다(Pure의 검사 안에서). - 줄 수가 같은 일을 하는 파이썬 참조 이하다. - C 대비 시간은 재고 기록하되 막대는 두지 않는다. 바이트와 비트의 일은 실행기로 돈다. 빠르게 하는 것은 핵이 선 뒤다. - 실행기만의 길에서 다섯 일(041)의 명령 수가 ±0.1%, 은행 질의의 명령 수가 ±1% 안이다. 새 명령은 기존 길을 바꾸지 않는다. - **검증.** 새 명령마다 경계(밖의 자리, 음수 시프트, 넘침)를 단위 시험으로 본다. 무작위 입력의 다이제스트는 파이썬 `hashlib`와 같아야 한다. ### 2단계: 중첩, 갈래, 재귀, 글자 (JSON) 크기 때문에 셋으로 나눈다(2026-10-13). 각각 재는 일이 있다. - **2a(만듦):** 형식 지정, 바이트 잇기와 자르기, 바이트와 text를 담는 `let`, `while`, `&&`와 `||`의 단락 평가. JSON을 받거나 거절하는 검사기로 잰다. 결과는 [140](../research/140-spec-suite.md)의 2a절이다. - JSONTestSuite에서 틀린 것이 없다. - 줄 수는 파이썬 이하다(80 대 90). - 시간은 C의 116배, 파이썬의 1.2배다. - 쓰다가 드러난 뜻의 틈 하나를 메웠다. `&&`가 양쪽을 다 계산해서 `i < n && d[i]`가 실패했다. 이제 단락 평가이고, 오른쪽이 실패할 수 없을 때는 지금처럼 AND 하나를 낸다. 그래서 TPC-C를 포함한 기존 모듈 35개가 바이트까지 같다. - **2b(만듦):** 레코드 안의 레코드와 리스트, 갈래와 `match`, 값으로서의 리스트, 수 읽기. 결과는 140의 2b절이다. - **꼴.** 레코드와 갈래는 aup가 경로 이름의 열로 펼쳐, 런타임에는 레코드 값이 없다. 리스트만 런타임의 묶음 값이다. - **칸.** 칸에는 이벤트가 만든 리스트의 번호(i64)를 담는다. 리스트의 내부, 묶음 실행기, JIT는 그대로다. JIT의 덧붙이기 빠른 길만 리스트 필드를 비킨다. - **값의 뜻.** 나눠 가진 리스트는 바뀌기 전에 복사되므로 값의 뜻이 지켜진다. 읽기는 레지스터에 묶어 기존의 걷기, 접기, 자리 읽기를 그대로 쓴다. - **JSON 트리.** JSONTestSuite에서 틀린 것이 없고, 무작위 문서 300개의 다이제스트가 파이썬과 같다. - **막대.** 줄 수는 못 미쳤다(167 대 133: 타입 선언). 시간은 C(cJSON)의 40배이고, 순수 파이썬보다도 1.3배 느리다. - **남은 것.** 메시지 필드의 아무 값(JSON 경계에서 바로 트리로)과 저장되는 리스트의 리스트는 3·4단계로 남긴다. - **2c(만듦):** 재귀(부르기와 돌아가기, 함수마다의 검증), 값으로서의 레코드, 글자로 읽기. 결과는 140의 2c절이다. - **꼴.** 함수는 CALL이 도는 블록이다. 매개변수를 스택에 두고 시작해 RETURN이 결과를 돌려준다. 부름마다 제 스택, 지역 칸, 그리고 함수의 코드가 이름을 대는 이벤트의 리스트, 레지스터, 이름으로 담은 값을 가진다. 부른 쪽의 것은 돌아올 때 그대로다. - **끝남.** 부름의 깊이는 프레임마다의 바이트를 이벤트의 몫에 세어 묶는다(넘으면 `memory`). 그 바이트는 구현의 크기가 아니라 표다(`module.h`의 `PURE_CALL_*`, 가스표처럼). 그래서 같은 모듈은 어느 기계, 어느 빌드에서나 같은 깊이에서 실패한다. - **값으로서의 레코드.** aup는 레코드와 갈래의 값을 필드들로 스택에 놓고, 이름 하나(필드마다 let)에 담고, 함수에 주고받는다. 항목은 함수에 값으로 넘어간다. 함수는 제 리스트를 가지므로 항목의 자리는 뜻이 없다. - **글자.** `for ch in t`가 text를 코드 포인트로 돈다(CHAR_AT). `char(n)`이 코드 포인트를 text로 짓는다(CHAR). - **JSON 트리.** 재귀 하강 파서와 깊이 우선 걷기로 다시 썼다. JSONTestSuite와 무작위 문서 300개가 파이썬과 같다. 10만 겹은 프레임이 몫을 다 써서 거절되고, 3만 겹의 올바른 배열은 받는다(파이썬의 `json`은 그 깊이를 못 읽는다). - **막대.** 줄 수는 못 미쳤다(142 대 105). 시간은 cJSON의 46배, 같은 설계의 순수 파이썬의 1.6배다. - **막은 구멍.** 반복 아래에 값을 둔 모듈이 검증을 지나 실행기를 죽였다(반복의 블록이 그 실행의 스택 바닥에서 시작해 64칸을 넘었다). 이제 LOOP와 EACH는 제 피연산자만 있는 스택에서 연다. aup가 만든 모듈에는 그런 꼴이 없었다. - **남은 것.** 작은 블록 fn의 인라인(JSON 트리의 부름 대부분이 `skip` 같은 작은 fn이다), 보기에서의 부름, 레코드 값의 `if` 합류, 저장 상태의 중첩(3단계). - 레코드의 필드와 리스트의 원소가 아무 값을 가진다. - 갈래와 `match`를 둔다. - 재귀하는 fn과 `while`을 둔다. Pure에는 부르기와 돌아가기, 함수마다의 검증이 생긴다. - `for c in t`로 글자(코드 포인트)를 읽는다. - 메시지의 필드가 아무 값을 가진다. JSON의 배열과 객체가 곧 리스트와 레코드다. - 1단계의 SHA-256이 길었던 까닭을 여기서 푼다. - 형식 지정(`{x:08x}`, `{price:.2}`) - 바이트 값을 잇고 자르기(`b + c`, `b[a .. c]`)와 바이트를 담는 `let` - 값으로서의 리스트: 블록 안에서 짓고 다시 짓는다 - fn을 펼칠 때 리터럴 인자를 접기: 구현의 일이다. 140이 블록당 명령의 39%를 fn 호출의 몫으로 쟀다 - 먼저 이벤트와 보기의 값(메모리 안)에서 한다. 상태는 3단계다. - **막대.** - JSON 파서가 JSONTestSuite의 `y_`를 모두 받고 `n_`을 모두 거절한다. `i_`는 정한 쪽을 적는다. - 줄 수가 파이썬 참조 이하다. - 시간을 기록한다. - TPC-C와 은행 질의가 그대로다(026의 중단 기준). ### 3단계: 중첩된 상태의 저장 - 중첩된 리스트의 이미지와 로그를 경로로 이름 붙인다. - 스냅숏의 나누기와 교체의 이름 잇기가 깊이에서도 그대로 성립해야 한다. - **막대.** 메모리 앱과 수시로 재시작하는 저장 앱이 매 단계 비트까지 같다(지금의 대조를 중첩으로 넓힌다). 커밋의 모든 단계에서 죽여도 전부 아니면 없음이다. **만듦(2026-10-18).** 결과는 140의 3단계절이다. - **고른 표현.** 계획(중첩 리스트마다 경로로 이름 붙인 이미지와 로그)과 다르게 했다. - 이벤트를 넘어 사는 리스트(저장, transient)의 칸이 리스트 값을 그 항목들의 저장 형태로 담는다(바이트처럼 담고, 이미지 머리는 종류 5로 적는다). - 읽으면 이벤트의 리스트 하나로 풀리고(ITEM_GET), 쓰면 그것에서 지어진다. - **그래서 따라오는 것.** 리스트 모듈은 그 칸을 text의 바이트처럼 다룬다. 그래서 이미 있는 기계가 깊이에서도 그대로다. - 이미지, 로그, 되돌리기, 읽는 쪽과의 나눔이 그렇다. - 커밋의 원자성과 죽음의 회복도 그렇다. 새 저장소 형식과 새 복구 길이 없다. - **이름 잇기.** 이미지의 머리가 레코드 그래프를 적는다(레코드가 리스트를 거쳐 자신을 담을 수 있어서 트리가 아니라 번호로 가리키는 표다). 시작과 교체는 그래프를 따라 이름으로 옮긴다. - **비용.** 중첩 리스트를 바꿀 때마다 그 값 전체를 풀고 짓고 쓴다. 바뀐 것이 아니라 그 값의 크기에 비례한다. - 큰 중첩 리스트를 덧붙여 짓는 것은 제곱이다. - 구조를 나누는 표현(노드를 나누는 트리, 경로로 이름 붙인 로그)은 뜻을 바꾸지 않고 나중에 고를 수 있는 구현이다. - **남은 것.** 메시지의 행이 리스트를 담는 것(JSON 경계에서 바로 트리로)은 4단계다. 접기 안에서 리스트 필드의 `.count`를 읽는 것도 아직 안 된다(레지스터를 묶어야 해서). ### 4단계: 패턴 모으기 - 앵커, 메시지 하나인 입력, 아무 값인 보기를 둔다. - 키별 문서와 고정 한도를 없애고, 메시지의 행을 중첩으로 옮긴다. - **막대.** - Pure, aup, Vega의 모든 검사를 옮긴 뒤 초록이다. - 명령과 상태 종류의 수, 코어의 줄 수가 줄었다(세어 적는다). - TPC-C와 은행 질의가 그대로다. **계획(2026-10-18).** 여섯 걸음으로 나눈다. - 걸음마다 지킬 것: Pure, aup, Vega의 검사가 초록이다. 기존 일의 명령 수(041의 다섯 일, 은행 질의)가 막대(1%) 안이다. 걸음마다 커밋한다. - 시작의 수(3단계 뒤, 9f17dd6): - 명령 106개 - 상태 종류 7개: 수, text, 커서, 리스트, 인덱스, bool, i64 - 입력 종류 6개: 키, 메시지, text, 타이머, 출력, 인자. 여기에 프레임 이벤트와 가리킴(`pure_app_point`)이 따로 있다. - 보기 종류 5개: 수, text, 표, bool, i64 - 메시지 필드의 타입 7개, 레코드 필드의 타입 5개 - 코어(`core/`의 시험이 아닌 파일) 22,398줄, serve 5,086줄 - **4a. 메시지의 리스트가 중첩을 받는다.** - 메시지의 행은 이미 이벤트의 리스트로 들어온다(let의 리스트와 같은 것). 그 레코드가 리스트를 담게 한다. - 페이로드는 3단계의 저장 형태 그대로다. JSON 경계는 객체 안의 배열을 그 형태로 짓는다. - 3단계가 남긴 것이다. JSON 경계에서 바로 트리로 들어온다. - **만듦(2026-10-18).** - Pure: 메시지가 도착할 때 리스트를 담은 행을 3단계의 해독기로 읽는다. 행마다의 리스트는 이벤트의 리스트가 된다. - serve: JSON 경계가 객체 안의 배열을 그 레코드의 객체들로 짓는다(리스트 64겹까지). - aup: 메시지의 행이 리스트를 담는 것을 받는다. 메시지의 행 안의 리스트에 덧붙이는 것은 거절한다(행은 읽기만 한다). - 찾아 막은 구멍: 3단계의 해독기가 4바이트가 남지 않은 길이를 넘겨 읽었다(`size - at - 4`가 넘쳐 돌았다). 저장한 것만 읽던 3단계에서는 드러나지 않았고, 밖에서 오는 메시지가 드러냈다. - 시험: - 단위 시험 `nested_rows`: 세 번의 이벤트, 온전하지 않은 행 넷이 아무것도 바꾸지 않는다. - Pure의 검사 `server_trees`: 무작위 숲 300개를 JSON으로 보내 같은 차례로 도는 파이썬 모델과 견준다. 거절 셋(리스트 필드가 배열이 아님, 없는 필드, 65겹)과 재시작도 본다. - 명령 수(b30 대 b29): 다섯 일이 묶음 BFS +0.12%(늘 오가는 범위) 말고는 ±0.001% 안이다. 은행 질의(s29 대 s30, 각 두 번)는 −0.00%에서 +0.23%다. - **4b. 키별 문서를 없앤다(026의 의심 4).** - 레코드 필드가 바이트를 받는다. 편집기는 `list of Doc by key`와 앱이 가진 리비전으로 쓴다. - Pure에서 컬렉션, DOC·DOC_SIZE·REPLACE, 키 있는 메시지와 기준 리비전, PureData의 문서 콜백, 문서별 묶음 저장이 빠진다. serve의 문서 모드(키마다 앱, ETag와 If-Match)도 빠진다. - 옛 편집기(editor.pure)와의 대조는 같은 뜻의 파이썬 모델과의 대조로 바꾼다. - 두 커밋으로 나눈다. - 4b-1: 레코드 필드의 바이트(Pure, aup). 새 편집기를 키 있는 리스트로 쓰고 모델과 견준다. 옛 문서의 기계는 아직 둔다. - 4b-2: 키별 문서를 지운다. 문서에 기대던 검사는 같은 것을 보는 다른 앱으로 옮긴다. - 파이프라이닝과 묶음 저장(kill -9 포함)은 새 편집기의 입력과 보기로 본다. - 빌려 읽기와 그 예산은 text 상태의 보기로 본다. text 상태도 저장된 조각을 빌려준다. - HTTP 벤치마크의 문서 묶음은 그 계약(axum, drogon과 같은 `/objects`)이 사라지므로 걷는다. 앱 묶음은 남는다. - 잃는 것: 키마다 따로 도는 앱(문서만 가진 모듈이 스레드마다 앱 하나)과 큰 문서의 조각 편집. 키 있는 리스트를 키로 나눠 돌리는 것과 큰 바이트 값의 조각 표현은 뜻을 바꾸지 않고 나중에 고를 수 있는 구현이다. - **4b-1 만듦(2026-10-18).** - 레코드 필드의 타입 5가 바이트다. text처럼 담고 UTF-8을 묻지 않는다. 함수 서명의 바이트(전의 PURE_SIGNATURE_BYTES)도 이 타입이 되었다. 바이트 필드는 키가 되지 않는다. 교체하면 text 필드는 바이트 필드로 값을 지닌 채 옮겨진다. - aup: `body: bytes`(기본값은 문자열의 바이트), `x.body.bytes`. 새 편집기 `examples/documents.aup`는 키 있는 리스트와 앱의 리비전으로 쓴다. - 시험: 단위 시험 `bytes_fields`(UTF-8이 아닌 바이트, text에서 바이트로의 교체, 거절 셋), aup의 `documents_model`(무작위 보기와 편집 160걸음, 씨앗 넷, 메모리와 여섯 번 다시 시작하는 저장소에서 모델과 1,280번 같음), `server_trees`의 바이트 필드. - 명령 수: 덧붙이기가 필드마다 타입을 두 번 묻던 것을, 로드가 만든 레코드마다의 비트 마스크(`record_held`) 하나로 바꿨다. 그래서 b32가 b30보다 적다(묶음 없는 BFS −1.05%, join −0.25%, 묶음 BFS −0.36%, 나머지 ±0.04% 안). 은행 질의(s30 대 s32)는 −0.01%에서 +0.30%다. - **4b-2 만듦(2026-10-18).** 모듈 형식 v7. - Pure: 컬렉션 표, DOC·DOC_SIZE·REPLACE(명령 106개 → 103개), 키 있는 메시지와 기준 리비전, PureData의 문서 콜백 여섯, 문서별 묶음 저장(키마다 한 번, 256개까지)이 빠졌다. `pure_app_message`는 입력과 페이로드만 받는다. 머리는 38바이트다. `pure-edit-module`도 빠졌다. - serve: `/objects` 경로, ETag와 `If-Match`·`If-None-Match`, 412, 문서만 가진 모듈의 스레드마다 앱이 빠졌다. 상태도 출력도 없는 모듈만 스레드마다 돈다. - aup: `bytes by key`, `message by key`, `이름[key]`, `.replace(..)`가 빠졌고 옛 편집기 예제 둘도 빠졌다. - 찾아 막은 구멍: 어느 블록에도 없는 명령(돌지 않는 코드)은 검증되지 않는데, 로드의 뒤 패스들이 모든 명령의 인자를 읽었다. 손상한 모듈의 시험이 머리가 바뀌어 다른 손상을 고르자 범위 밖을 읽고 죽었다. 이제 로드가 그런 명령을 CONST 0으로 지운다. - 옮긴 검사: 옛 문서 서버와의 대조(`server_parity`)는 키 있는 리스트의 문서 앱(`DOCS_SOURCE`)과 파이썬 모델의 대조 (`server_documents`, 연결 스레드 하나와 셋, 재시작)가 되었다. 파이프라이닝과 묶음 저장(kill -9)은 그 앱의 입력과 보기로, 빌려 읽기의 예산은 text 보기로 본다(문서의 빌려 읽기는 `server_lent_views`가 이미 text 보기로 본다). 시계 검사는 그 앱으로 편집한다. aup의 옛 편집기 대조(`editor_parity`)는 `documents_model`이 대신한다. - HTTP 벤치마크의 문서 묶음은 옛 문서 서버와 기준선만 돈다. - 잃은 것: 저장소의 그룹 커밋(여러 기록자가 한 저장소에 커밋을 모으는 것)을 pure-serve가 더 쓰지 않는다. 앱마다 소유자 하나가 저장하기 때문이다. 그 길은 옛 문서 서버와 저장소의 시험에 남는다. - 줄 수: 코어 22,398 → 22,204(4a와 4b-1의 더함을 넣고), serve.c 5,086 → 4,978, aup의 compiler/ 5,923 → 5,829(compile.c는 4b-1 뒤 5,689 → 5,587). - 명령 수(b33 대 b32): 묶음은 BFS −0.33%, 나머지 ±0.002% 안이다. 실행기만의 길은 BFS +0.63%, knapsack +0.52%, join +0.38%다 (`step`에서 경우 셋이 빠져 코드 생성이 움직였다). 은행 질의(s32는 v6 모듈, s33은 v7 모듈로, 각 두 번)는 field +0.68%, found +0.44%, large +0.42%, 나머지 +0.13% 안이다. 모두 막대(1%) 안이지만 4단계 끝에 다시 센다. - **4c. 커서와 인덱스를 앵커 하나로(의심 3).** - 상태 종류 "앵커"가 text나 리스트 하나를 가리킨다. - 뜻은 그대로다: 편집을 따라가고, 글자의 경계에 서고, 지운 항목에서는 이웃으로 간다. - 같은 일을 하는 커서와 인덱스의 명령은 하나로 합친다. - **설계(2026-10-18).** - 둘은 같은 것이다. 열(text의 글자, 리스트의 항목) 안의 틈 하나이고, 편집을 따라 움직인다. - 커서는 틈에 선다. 그 자리에 넣으면 뒤로 가고(마크는 남는다), 지운 범위 안이면 그 처음으로 간다. - 인덱스는 항목 앞의 틈에 서서 그 항목을 가리킨다. 그 자리에 넣으면 뒤로 간다(항목을 따라간다). 지운 항목에서는 다음 항목을 가리킨다. 끝의 틈에는 서지 않는다(빈 리스트가 아니면 마지막 항목으로). - 형식의 앵커 명령은 일곱이다. 모두 인자에 대상(text나 리스트 상태)을 적는다. - LOAD, STORE: 앵커 상태 - START, END: text의 처음과 끝, 리스트의 첫 항목과 마지막 항목 - MOVE(단위): 글자, 줄, 항목으로 n만큼. 끝에서 멈춘다. - COUNT(단위): 앵커 앞의 단위 수. 줄은 0부터 센다(aup의 `at.line`이 1을 더한다). - AT(단위): 처음부터 n 단위 뒤의 앵커. 없으면 `not_found` - 지금의 열셋(CUR_* 여섯, INDEX_* 일곱)이 일곱이 되고, 상태 종류가 일곱에서 여섯이 된다(bool과 i64의 번호가 당겨진다). - 구현은 대상의 종류로 나뉜다. 로드가 형식의 명령을 대상에 맞는 내부 명령(지금의 CUR_*와 INDEX_*의 걸음)으로 바꿔 쓰고, 검증은 그것에 대해 지금처럼 한다. 그래서 실행기, 묶음 실행기, JIT의 길은 그대로다. 026의 원칙(뜻은 일반적으로, 특수화는 구현 아래로)이다. - **4d. 입력은 메시지 하나(의심 1).** - 키, text, 타이머, 프레임, 가리킴은 표면과 호스트가 만드는 메시지다. - 모듈의 입력은 메시지(필드들)와 표면이 읽는 묶음(키 이름, text, 주기, 프레임)이다. 묶음을 해석하는 것은 표면이고, Pure는 메시지만 받는다. - `dt`는 프레임 메시지의 필드가 된다. 키의 누름과 뗌은 메시지의 bool 필드다. - **4e. 보기는 아무 값(의심 2).** - 보기의 타입은 레코드 필드의 타입이다. 표는 레코드의 리스트를 내는 보기이고, 열은 그 레코드의 필드다. - 게으른 칸 계산과 한 번 훑는 집계는 그 구현으로 남는다. 항목이 리스트를 담을 수 있고, 보기의 본문이 지은 리스트도 낼 수 있다. - **4f. 고정 한도를 뺀다(의심 7).** - 상태, 입력, 레코드, 필드, 중첩의 수를 모듈에 맞춰 잡는다. 비트 하나씩 쓰던 집합은 길이를 가진다. - aup 소스의 32 KB도 뺀다. - 앞의 걸음들이 표를 바꾸므로 마지막에 한다. - **차례의 까닭.** - 4a는 작고, 3단계의 끝을 맺는다. - 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다. - 4c는 가리킴(4d)이 무엇을 옮기는지 정한다. - 4e는 표면(Vega)이 읽는 쪽을 바꾸므로, 입력 쪽(4d)과 따로 잰다. ### 5단계: 수의 모델 - 정수 리터럴의 기본, `/`와 `//`, 돈의 십진수, 묶음 실행기의 2^53 검사를 정한다. - **막대.** TPC-C가 SQLite와 행마다 같은 상태를 남기는 것이 그대로다. **순서의 까닭.** - 1과 2가 묶음의 명세 대부분을 연다. 가장 값이 크다. - 3은 중첩을 상태까지 넓힌다. - 4는 쌓인 것을 줄인다. 2 뒤에 해야 모을 곳(중첩, 갈래)이 생긴다. - 5는 모든 앱의 수를 건드리므로 마지막이다. ## 선행 - **EVM의 가스.** 끝없는 반복과 재귀를 허락하고, 끝남과 결정성을 가스로 얻는다. 가스가 다하면 그 호출의 상태 변경이 되돌아간다. 가스는 명령마다 명세의 표로 정한다. 모든 노드가 같은 가스를 센다. Pure의 예산, 원자성, "모듈이 단위"와 같은 꼴이다. - **파이썬의 정수 비트 연산.** 무한한 2의 보수 위의 뜻이다. Pure는 i64 안에서 같은 뜻을 쓰고, 넘으면 실패한다. - **Elm.** 모델, 업데이트, 뷰와 레코드, 커스텀 타입(갈래)이다. aup 앱의 모양과 같다. 영속, 트랜잭션, 전달은 없다. - **영속 자료 구조**(Clojure의 벡터와 맵). 구조를 나눠 스냅숏을 싸게 한다. Pure의 공유 리스트 사본이 그 한 예다(B+트리). ## 의심 (이 설계 안의) - **바이트와 text를 따로 둘 것인가?** 표현은 하나이고 타입만 둘이다. 불변식(UTF-8)이 보기와 경계에 필요해서 타입은 남긴다. - **앵커가 필요한가?** 항목에 키가 있으면 인덱스는 키다. text의 커서는 편집에 따라 옮겨야 하므로 남는다. 4단계에서 정한다. - **`number`(binary64)는 앱 런타임에 필요한가?** 물리와 그래픽에는 필요하다. 돈에는 맞지 않는다(5단계). - **세는 `for`가 필요한가?** 뜻으로는 `while`과 예산이면 된다. 다만 세기는 읽기 쉽고, 상한을 구현에 알려 준다. 남긴다. ## 1단계의 착수 기준 - **바꿀 결정.** 바이트, 비트, 수의 리스트로 SHA-256을 얇게 쓸 수 있으면 받아들이고, 2단계로 간다. - **남은 질문.** - 새 명령이 실행기, 묶음 실행기, JIT의 기존 길을 바꾸지 않는가? - 한 블록에 드는 명령 수와 시간은 얼마인가? - **최소 확인.** aup로 쓴 SHA-256, NIST 벡터, 파이썬 참조의 줄 수, C 대비 시간, 다섯 일과 은행 질의의 명령 수. - **중단 기준.** 기존 일의 명령 수가 막대를 넘으면 멈추고, 새 명령이 기존 길을 건드린 곳을 찾는다. SHA-256이 파이썬보다 길면, 무엇이 길게 했는지 적고 그것을 1단계에서 고칠지 정한다.