1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
# 016 — 참·거짓과 정확한 정수: `bool`과 `i64`
작성일: 2026-09-30. 상태: 1단계(`bool`)와 2단계(`i64`) 구현, 검사로 고정.
aup의 v0c에 남은 것(aup `docs/design/002-dsl-direction.md`의 `i64 bool`)이고,
[015](015-boundary-meaning.md)가 정한 경계마다의 값의 뜻에 두 종류를 더한다.
## 문제
- **참·거짓이 수다.** 비교와 논리 연산은 0이나 1을 내고, 0이 아니면 참이다. 그래서 할 일의 `done`은 `done = 0`인 수
필드다. 보기의 JSON에는 `true` 대신 `1`이, 화면에는 `1`이 나온다. `done + 1`도 타입 오류가 아니다. 조건 자리에 아무
수나 들어간다.
- **정수가 2^53에서 멈춘다.** 수는 binary64다. 밖에서 오는 64비트 ID(많은 서비스의 ID가 2^53을 넘는다), 나노초 시각(2^53
나노초는 104일), 큰 누계는 정확히 들지 못한다. 015는 수가 비트까지 그대로라고 정했지만, 그 비트가 정수를 담지 못한다.
## 원칙
aup의 원칙(002)을 따른다. 위치가 정수가 아니듯 참·거짓은 수가 아니고, 길이에 단위를 적듯 정수는 정확하다. 둘 다 Pure
검증기의 스택 종류가 되어, 검증된 모듈은 실행 중에 타입을 검사하지 않는다(008).
## 1단계: `bool`
**언어(aup).**
- 타입 `bool`, 리터럴 `true`와 `false`.
- 비교(`< <= > >= == !=`, text 비교), `has`, 논리(`&& || !`)가 bool을 낸다. `&&`와 `||`는 지금처럼 양쪽을 모두 계산한다.
- 조건 자리(`if`, 조건식, `where`, `require`, `count`와 `first`의 조건, 걸러진 표)는 bool만 받는다. 수를 주면 진단이
고치는 법을 말한다(`x != 0`).
- bool끼리 `==`와 `!=`는 된다. 크기 비교, 산술, `sum`·`max`·`min`·`distinct`는 안 된다. 키 있는 목록의 키가 될 수 없다.
- 선언: `state on = false`, 레코드 필드 `done = false`, 메시지와 출력의 필드 `ok: bool`, 보기와 열은 bool 식이면 bool이다.
- 보간(`"{done}"`)은 `true`나 `false`다.
**모듈 형식 v6(Pure).** 판을 올린다. 비교의 결과 종류가 바뀌므로 v5 모듈은 다시 빌드한다.
- 검증기의 스택 종류에 bool을 더한다. 실행 중의 값은 수 칸의 0과 1이다.
- 새 명령은 없다. CONST의 인자 1이 bool 상수(값 +0이나 1)이고, NUM_TEXT의 인자 1이 bool을 `true`·`false`로 쓴다.
LOAD·STORE와 지역 칸은 수와 bool을 함께 다룬다(상태의 타입이나 칸의 담는 것이 정한다).
- bool을 내는 명령: LT–NE, TEXT_COMPARE, LIST_HAS, AND·OR·NOT. EQ와 NE는 bool 둘도 비교한다.
- bool을 받는 명령: JZ, REQUIRE, AND·OR·NOT, 집계의 포함 여부(count와 first는 bool 하나, sum·max·min·distinct는 수와
bool), 걸러진 표의 필터.
- 새 타입 번호: 상태 5(bool, 8 B 값은 f64 0이나 1), 레코드 필드 2(bool, 기본값 f64 0이나 1), 메시지 필드 6(bool, 접두부의
1바이트 0이나 1), 보기 3(bool). 지역 칸의 담는 것 2(bool). 상수, 초기값, 기본값은 +0이나 1이어야 한다.
- 메시지의 bool 필드 바이트가 0이나 1이 아니면 메시지가 `limit`으로 실패한다(도착할 때, 015의 거절과 같은 자리).
**경계(015에 더한다).**
- JSON: bool 필드는 `true`·`false`만 받는다(`0`, `1`, `"true"`는 거절). 보기와 표의 bool은 `true`·`false`다.
- 쿼리: `true`·`false`.
- 저장: 목록의 bool 필드는 수 열의 0과 1이다(저장 형식도 수다). 레코드의 필드 타입이 뜻을 정한다.
- 전달: 링크의 FIELDS와 HTTP 전달의 JSON이 bool을 bool로 싣는다. 받는 입력의 필드도 bool이어야 한다. 넓히기는 없다.
- 모듈 교체: 수 필드나 상태가 bool이 되면 0(−0도, 015)과 1은 `false`와 `true`로 옮기고, 다른 수는 기본값이 된다. bool이
수가 되면 `false`와 `true`는 0과 1이다. 저장소에서 다시 열 때와, 교체가 출력의 필드를 바꿀 때 기다리던 보냄도 같다.
- 화면(Vega): 글자로는 `true`·`false`다(보간과 같다). 앱이 다르게 보이고 싶으면 조건식으로 글을 고른다.
**옮기는 비용.** 예제의 수 깃발을 bool로 바꾼다(`done`, `urgent`, `running`, `holding`). 저장된 0과 1은 위의 교체 규칙으로
그대로 이어진다.
### 1단계에서 알게 된 것
- **새 명령은 필요 없었다.** 처음 설계는 BOOL_CONST와 BOOL_TEXT를 더하려 했다. CONST와 NUM_TEXT의 인자 하나(0이던
자리)로 충분했다. 검증기의 종류와 타입 번호만 늘었고, 실행기는 bool을 수의 칸의 0과 1로 그대로 돈다(잎 단위 실행도).
- **저장된 보냄의 bool을 거절하던 곳이 있었다.** 출력의 필드 타입을 저장소에서 다시 읽는 검사가 `>= 행`으로 bool(6)까지
막아, bool 필드를 가진 출력의 보냄이 기다리는 노드는 다시 시작하지 못했다. `server_meaning`의 재시작 출구가 잡았고,
고쳤다. test-module이 잡지 못한 까닭은 bool 보냄을 저장하고 다시 여는 경우가 없어서였다. 그 경우를 더했다(고치기 전의
코드로는 실패한다).
- **−0은 `false`로 옮긴다.** 수가 bool이 될 때 0과 1만 옮기기로 했는데, 015가 0에는 부호가 없다고 정했으므로 −0도 0이다.
처음 구현은 −0을 기본값으로 보냈다.
- **JSON의 `True`는 타입이 아니라 문법의 거절이다**(`json`). 1, 0, `"true"`, `null`, 배열은 `json_type`이다.
검사: Pure test-module에 bool 검증 거절 28가지와 실행·메시지 바이트·보냄·링크·교체·저장(보냄 포함)을 더했고, 레인 대조의 무작위
생성기가 수와 bool을 나눠 만든다(3,600 집계가 비트까지 같다). `server_meaning`은 값 67개마다 bool 필드를 싣고 나가는 길 일곱에서
본다(거절 52가지, bool은 거절의 종류까지). HTTP에서 bool 보기의 글자와 bool 인자도 본다. aup는 bool 진단 20가지, 받아들이는
형식, 출력마다 `true`·`false`인지 보는 검사(파이썬의 `True == 1`을 같게 보지 않는 비교)를 더했다. Vega는 bool 보기가 CLI, TUI,
문장, 웹에서 `true`·`false`로 보이는지 본다. 커밋: Pure `86fd4f7`·`d1c90e2`·`d415a5a`, aup `b8e3faa`, Vega `72ecae6`.
## 2단계: `i64`
1단계와 같은 방식이다. 판은 올리지 않는다. 새 명령, 새 타입 번호, 0이던 자리의 값만 더하므로 v6 모듈은 그대로 유효하다.
**언어(aup).**
- 타입 `i64`. 상태 `state n: i64 = 0`(값을 생략하면 0), 레코드 필드 `id: i64`(기본값 0, `= 5`도 된다), 메시지·출력·인자의
필드 `id: i64`. `let`은 값의 타입을 따른다.
- 정수 리터럴(점과 지수가 없는 것)은 문맥의 타입이 된다. 연산이나 비교의 다른 쪽, 대입이나 필드의 자리가 문맥이다. 문맥이
없으면 수다. 글자 그대로 정확하게 읽는다(`9007199254740993`). i64 범위를 넘는 리터럴이나 i64 자리의 소수는 진단이다.
- `+ - *`와 단항 `-`는 넘치면 사건이 `overflow`로 실패한다. `/`와 `%`는 내림한다(나머지는 나누는 수의 부호를 따른다). 0으로
나누면 `divide`다.
- 수와 섞지 않는다. `i64(x)`는 x가 정수이고 범위 안일 때, `number(i)`는 |i| ≤ 2^53일 때만 되고, 아니면 사건이 `limit`으로
실패한다.
- 비교, `min`·`max`, 집계(`sum`은 넘치면 `overflow`), 키 있는 목록의 키(`by id`), 조각(`xs[lo .. hi]`), 보간(정확한 자릿수)이
된다.
**모듈(Pure).**
- 실행 중의 값 칸은 수와 i64를 같은 8바이트에 겹쳐 든다. 스택, 상태(`PureApp.values`), 목록의 칸(`PureCell`), 지역 칸, 명령의
상수가 그렇다. 옮기는 명령(LOAD, STORE, ITEM_GET·ITEM_SET, LIST_APPEND, 지역 칸, PICK, SLIDE, FIELD, SEND)은 비트를 그대로
옮기고, 계산하는 명령만 종류를 본다. 검증기가 종류를 보장한다.
- 새 명령을 뒤에 붙인다. I64_ADD, I64_SUB, I64_MUL, I64_DIV, I64_MOD, I64_NEG, I64_COMPARE(인자는 관계, TEXT_COMPARE처럼),
I64_MIN, I64_MAX, I64_FROM(수 → i64), NUM_FROM(i64 → 수). CONST의 인자 2는 i64 상수(값의 8바이트가 정수)이고, NUM_TEXT의
인자 2는 i64의 글자다. 새 실패 종류 `overflow`.
- 타입 번호: 상태 6, 레코드 필드 3, 메시지 필드 7(접두부의 8바이트, 부호 있는 little-endian), 보기 4, 지역 칸의 담는 것 3.
- 집계: FOLD의 인자에 PURE_FOLD_INTEGER(0x40)가 있으면 sum·max·min·distinct가 i64 값을 받는다(sum·max·min의 결과는 i64).
i64가 든 블록은 잎 단위로 돌지 않고 한 항목씩 돈다(필요해지면 뒤에 더한다). distinct의 집합은 빈 칸 표시를 따로 둔다
(지금의 표시 `UINT64_MAX`는 i64의 −1이다).
- 키 있는 목록: i64 키는 정수로 비교한다. 찾기는 안내 키 없이 잎의 열을 이진 탐색한다(안내 키는 binary64라 정수에 맞지 않는다.
정수 안내 키는 뒤의 일이다, [109](../research/109-finding-items.md)의 "다음"). 키 범위의 자동 경계(008의 키 범위)는 i64 키에
아직 쓰지 않는다. 조각은 i64 두 개를 받는다.
- 보기: `pure_app_view`는 수와 bool을 주고, 새 함수가 i64를 준다.
**경계(015에 더한다).**
- JSON과 쿼리: i64 필드는 `-?(0|[1-9][0-9]*)`(부호 `+`, 앞의 0, 점, 지수 없이)이고, −2^63부터 2^63−1까지다. 그 밖은 u32처럼
거절한다(JSON은 `json_type`, 쿼리는 `query`).
- 보일 때: 정확한 10진 자릿수다. 보기의 글자, 표의 JSON(JSON의 수로, 자릿수 그대로), HTTP 전달의 JSON, `pure-run`, 화면이 모두
같다. 자바스크립트의 `JSON.parse`처럼 binary64로 읽는 쪽은 2^53 너머에서 값을 잃는다. 그런 쪽에 보일 i64는 앱이 글자로
보인다(보간).
- 저장: 상태는 `values` 문서의 종류 6과 8바이트 정수다. 목록은 종류 4(i64)의 열이다. 이미지는 8바이트, 로그는 크기와
상관없이 zigzag varint다.
- 전달: u32는 i64로 넓힌다(정확하다). i64와 수, bool 사이는 넓히지 않는다(받지 않고 기다린다).
- 모듈 교체와 다시 열기: 수가 i64가 되면 정수이고 범위 안일 때 그 값, 아니면 기본값이다. i64가 수가 되면 |i| ≤ 2^53일 때 그
값, 아니면 기본값이다. bool과 i64는 0과 1로 옮긴다(i64의 다른 값은 bool의 기본값이 된다).
**착수 기준**(구현하기 전에 정해 커밋한다).
- 바꿀 결정: 위 모양대로 넣을지. 값 칸을 겹치는 것이 수의 경로를 느리게 하면 그 모양을 다시 본다.
- 최소 확인:
- test-module: 검증기의 거절, 산술의 끝값(±2^63 부근, 내림 나눗셈과 나머지의 부호, MIN/−1), 변환, 상태·필드·보기·보냄·지역
칸, i64 키 목록의 순서와 찾기·조각, 집계와 넘침, 저장·체크포인트·로그의 끝값, 교체.
- `server_meaning`에 i64 필드. 끝값(0, −1, 2^53+1, 2^63−1, −2^63)이 모든 길에서 같고, 거절할 글자(`1.0`, `1e3`, `+1`, `01`,
`"1"`, 2^63, −2^63−1)는 모든 들어오는 길이 거절한다.
- aup: 진단(섞기, 리터럴의 범위와 소수), 리터럴의 타입, 참조 모델. Vega: i64 보기의 자릿수.
- 통과 조건: 모두 초록이고, 수의 경로는 그대로다. 은행 질의([091](../research/091-queries.md))와 TPC-C(RAM, W=1)를 전과 후
번갈아 여섯 쌍 재서, 중앙값이 3% 넘게 느려지지 않는다.
### 2단계에서 알게 된 것
- **리터럴의 타입은 다른 쪽이 정한다.** `1 + x`에서 1을 내보낼 때는 x의 타입을 아직 모른다. aup는 다른 쪽을 시험 삼아
컴파일해 타입만 알고 되돌린다. 리터럴만의 식(`60 * 60 * 1000`, 리터럴을 고르는 `if`)도 통째로 다른 쪽을 따른다. 처음
구현은 시험 컴파일이 겹치면 한 겹마다 두 배가 됐다(괄호 22겹에 0.25초). 원하는 타입을 아래로 넘겨 식마다 한 번만 보게
했다(30겹에 1 ms 미만). aup의 검사가 30겹을 지킨다.
- **문맥이 없는 리터럴은 수로 남는다.** 그래서 있던 프로그램의 뜻이 바뀌지 않는다. `-7 / 2`는 전처럼 −3.5이고, i64의 내림
나눗셈은 한쪽이 i64일 때다(`i64(-7) / 2`는 −4). i64를 쓰지 않는 프로그램은 모듈이 바이트까지 같다. i64 전과 후의 aup가
예제 18개(aup 10, Vega 8), TPC-C, 은행, 은행의 질의 넷을 같은 바이트로 빌드한다.
- **값 칸을 겹치는 것은 따로 했다**(`600f576`). 옮기는 명령이 비트를 복사하고 계산하는 명령만 수로 읽게 바꾼 것만으로는
모든 검사가 그대로였다. i64는 그 위에 더했다.
- **옮기는 규칙은 하나다.** 모듈 교체, 저장된 값과 항목을 다른 모듈이 읽을 때, 기다리던 보냄을 옮길 때가 모두
`pure_cell_as` 하나를 쓴다. 수·bool·i64 사이에서 맞으면 옮기고 아니면 기본값이라는 규칙이 한 곳에 있다. 실패의 이름도
세 표에 나뉘어 있던 것을 `pure_fault_name` 하나로 모았다(`overflow`가 새로 생겼다).
- **수의 경로.** 기준을 채웠다(TPC-C 처리량의 중앙값 −0.6%, 은행 질의 0.0%에서 +2.2%). 시간으로는 가리지 못하는 1–3%는
명령을 세어 봤다. i64가 수의 경로에 더한 두 곳을 걷어냈다. 잎 단위의 폴드가 항목마다 i64인지 묻던 것과, 인라인되지 않게 된
칸 읽기다. 남은 것은 명령 +0.4%에서 +1.4%다([110](../research/110-counting-instead-of-timing.md)).
검사: Pure test-module에 i64 산술의 끝값 28가지와 검증 거절 14가지, 키 있는 i64 목록의 앱(순서, 넘치는 합, 되돌린 추가,
8바이트 메시지와 보냄, 모든 자릿수), 옮기기와 교체, 메모리와 대조하는 저장 셋(재시작, 이미지 46개)을 더했다. `server_meaning`은
값마다 i64 필드를 싣고 끝값 13개를 나가는 길 일곱에서 본다(거절 84가지, i64는 거절의 종류까지). aup는 i64 진단 18가지, 받아들이는
형식(30겹의 식이 선형인지도), 2^53 너머와 끝값을 사건마다 보는 검사, 생성 프로그램의 i64 값을 더했다. Vega는 i64 보기가 CLI,
TUI, 문장, 웹에서 모든 자릿수로 보이는지 본다. 커밋: Pure `600f576`·`8c92f4c`·`fc65d66`·`fcc1f67`, aup `24d95e1`, Vega
`49e0df3`.
## 1단계의 검사
- aup: 진단(수를 조건으로, bool 산술, bool 크기 비교, bool 키), 받아들이는 형식, 참조 모델.
- Pure: test-module(검증기가 bool을 섞은 모듈을 거절한다, bool 상태·필드·보기·보냄), `server_meaning`에 bool 필드(JSON,
쿼리, 링크, HTTP 전달, 재시작, 교체).
- Vega: bool 보기가 표면마다 `true`·`false`로 보인다.
|