세 가지를 자율 결정으로 닫고 타입 검사까지 세웠다.
1. scope 부모 문법: scope 자식 = 부모 { ... }로 확정. 부모를 적지 않으면
자식의 부모가 "가장 가까운 스코프"가 되는데 그것이 정확히 ambient
authority다. 권한 사슬이 main의 루트 TaskScope부터 끊기지 않으려면 모든
자식이 부모를 이름으로 지목해야 한다. 이름 해소가 이 구멍을 잡아준 건이라,
주석 처리했던 중첩 예제를 되살렸다.
2. prelude: 타입 이름은 그 타입에 딸린 함수의 이름공간이다(String.len,
File.close). 정적 메서드를 위한 별도 문법을 두지 않는다.
3. 타입 검사: 이 모듈 안에서 아는 것만 검사한다. 외부 이름은 TUnknown이
되어 무엇과도 맞는다 — 모르는 것을 틀렸다고 말하지 않기 위해서다.
제네릭 해소는 호출 지점의 지역 unification이고 함수 하나를 넘지 않는다.
클로저 파라미터 타입은 기대 타입에서 읽어온다(양방향 검사, 로컬).
단계 소유권을 하나 정정했다. affinity는 타입 동등성의 일부가 아니다. 값이
affine인지는 무엇을 capture했는지로 정해지는 substructural 성질이고
move/affinity 검사가 소유한다. 타입 검사가 이걸 판정하려다 정당한 코드를
거부하는 것을 06에서 확인하고 unify에서 분리했다.
unify 버그 하나: 같은 미지수끼리 unify할 때 occurs check가 자기 자신을
발견해 실패하고 있었다. 02의 fold 호출에서 잡혔다.
09_type_errors.cool 추가 — 외부 타입이 하나도 없어 검사기가 TUnknown으로
빠져나갈 구석이 없는 파일이다. 18개 진단이 전부 잡히고, 첫 오류에서 멈추지
않고 모두 보고한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
이 단계가 답하는 질문은 하나다 — 이 모듈 하나만 보고 무엇을 결정할 수 있는가.
그 경계를 산출물로 만들었다.
결정할 수 있는 것은 오류로 보고한다: 중복 정의, 중복 파라미터·제네릭,
선언되지 않은 effect 변수, 같은 블록의 재바인딩, 불변 바인딩에 대한 대입,
모듈에 없는 reexport 대상, 지역 바인딩이 아닌 scope 이름, variant 인자 개수.
결정할 수 없는 것은 외부 참조로 기록하고 오류로 만들지 않는다. 모듈 로딩이
아직 없으므로 해소할 방법이 없고, 이 목록이 곧 모듈의 의존 표면이자
interface hash가 소비할 입력이다. cool deps로 볼 수 있다.
이름 해소가 아니면 못 하는 판정 하나를 구현했다: match의 맨 이름이 바인딩인지
인자 없는 생성자인지는 구문으로 갈리지 않는다. enum 선언에서 만든 생성자
표로 판정하고, 같은 표로 인자 개수도 검사한다.
샘플에서 실제 오류 둘을 잡았다:
- 03의 scope inner가 어디서도 오지 않는다. scope X { }의 X가 "이미 가진
TaskScope를 쓴다"인지 "새 자식 스코프를 만들어 X로 묶는다"인지가 정해지지
않은 탓이다. 후자라면 자식의 부모가 구문에 없어 ambient authority가 된다.
결정 전까지 해당 예제를 주석으로 두고 이유를 적었다.
- 03이 List를 import 없이 쓰고 있었다. 외부 참조 목록에 드러나 채웠다.
TaskScope가 use 값이라는 사실에서 검사 하나가 따라 나온다: scope의 머리는
모듈 수준 이름일 수 없고 반드시 지역 바인딩이어야 한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
grammar.ebnf의 프로덕션 하나에 함수 하나로 대응한다. LL(1)이므로 선읽기는
항상 한 토큰이고 backtracking이 없다. 모든 노드가 위치를 들고 다닌다.
문법과 얽히는 두 자리를 원칙대로 처리했다:
- NEWLINE 흡수는 문법에 { NEWLINE }으로 적힌 자리에서만 한다. 파서가
"여기선 줄바꿈 무시" 식으로 임의 판단하면 어디서 무시되는지 아무도 모르게
된다.
- struct 리터럴은 if/match/scope 머리에서 금지하고(no_struct), 괄호·인자
목록·블록에 들어가면 다시 허용한다.
문법에 새긴 제한이 실제로 파서에서 죽는 것을 확인했다. 파라미터 위치의
effect 합집합과 match 가드는 검사기가 아니라 파서가 거부하며, 진단이
원인을 직접 말한다. 후행 콤마 누락도 일반적인 "닫는 괄호 필요" 대신
"다중 줄 목록에는 후행 콤마가 필요합니다"로 보고한다.
샘플을 실제로 파싱해 두 가지를 잡았다:
- 02와 05가 own을 타입 위치에 쓰고 있었다. 확정한 규칙은 바인딩 수식어가
이름 앞이므로 샘플이 틀렸다. 수정.
- 05에 구문 오류와 검사기 오류가 섞여 있었다. 파서가 첫 오류에서 멈추면
검사기 케이스에 영영 도달하지 못하므로 08_syntax_errors.cool로 분리.
grammar.ebnf를 구현과 맞췄다: 제네릭 인자에 effect 집합 허용, 마지막 문의
구분자는 "}" 앞에서 생략, 중괄호 목록 안의 NEWLINE 흡수 위치 명시.
cool ast 추가. cool check는 이제 파서까지 돌리되 여전히 통과했다고 말하지
않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
grammar.ebnf의 어휘 절을 구현한다. 렉서는 선읽기를 요구하지 않고,
문법과 얽히는 유일한 부분인 NEWLINE 삽입은 can_end_statement 하나로
판정한다. 모든 토큰이 위치를 들고 다닌다 — 진단 품질이 헌법급이므로
나중에 붙이지 않는다.
샘플을 실제로 렉싱해 함정을 하나 잡았다. effects 절이 줄 끝에 오면 "}"가
값 종료 토큰이라 NEWLINE이 삽입되어 다음 줄의 "->"와 끊긴다(Go ASI와 같은
형태). 렉서에 문맥을 주는 대신 — 렉서 피드백은 철학 2가 배제한다 —
시그니처 머리의 흡수 위치를 프로덕션에 명시적으로 적어 닫았다. 파서가
임의로 건너뛰는 것이 아니라 문법에 적힌 자리에서만 흡수한다.
다중 줄 목록의 후행 콤마 필수도 함께 명시(콤마로 끝난 줄은 NEWLINE을
만들지 않으므로 목록이 자연히 이어진다).
cool check는 어휘 분석을 돌리되 통과했다고 말하지 않는다. 파이프라인의
나머지가 없는 이상 그 파일은 검사된 것이 아니다. 디버깅용 cool tokens 추가.
테스트 30건: 키워드, 두 글자 연산자 우선, NEWLINE 삽입 6가지 경우,
리터럴과 이스케이프, 오류 7종과 오류 위치, 그리고 samples/*.cool 전체가
어휘 분석을 통과하는지.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
열려 있던 문법 항목을 언어 철학에 따라 전부 닫고 EBNF로 고정한다.
- 제네릭 인자를 대괄호로 통일(List[a], map[Int, String](...)). <>는 식
위치에서 비교 연산자와 갈리지 않아 turbofish라는 제2 표기를 부르는데,
effect 규칙이 명시적 인스턴스화 문법을 요구하므로 회피할 수 없다.
대괄호는 전위=리터럴 / 후위=인스턴스화로 위치가 결정해 LL(1)이고 표기가
하나다. 대가로 인덱싱 연산자를 두지 않는다.
- 파라미터 규칙을 callable 한정에서 전 파라미터로 일반화. 기본은 빌림,
own만 소유 이전. capability를 예외로 두지 않는 이유는 capability의 존재가
이미 타입과 effects 절에 드러나기 때문이다 — 표기가 실을 정보는 소유 이동뿐.
- 문 구분은 줄바꿈(Go식 자동 삽입), 블록은 식, return은 조기 탈출 전용.
if/match를 식으로 두면 재대입이 줄어 move 검사가 단순해진다.
- match 가드 없음. 가드는 exhaustiveness를 흐리고 SMT 없이는 _ 분기를
강요한다. 철학 1의 대표 항목을 문법 편의와 바꾸지 않는다.
- ?는 Result 전용 하드코딩. 조기 탈출도 블록 종료이므로 scope의 join/cancel이
? 경로에서도 실행된다. v1 linear 도입 시 이 경로가 암묵 drop 문제와 만난다.
- 수식어 순서는 바인딩(own, mut) 먼저, 타입(affine) 나중.
docs/grammar.ebnf는 LL(1)을 설계 제약으로 명시하고, effect 합집합을
결과 위치 전용 프로덕션으로 새겨 파라미터 위치의 역산을 파서가 거부하게 한다.
samples/는 확정 표기로 전면 갱신.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
샘플을 먼저 써서 EBNF 이전에 체감을 확인했고, 거기서 강제된 결정을 문서에
되먹인다.
- callable 파라미터의 기본을 use(비탈출)로 뒤집고 저장하는 쪽을 own fn으로
유표기. 반대로 두면 클로저를 저장하지 않는 고차 함수 — 표준 라이브러리의
거의 전부 — 가 use로 도배된다. 무표기-흔함/유표기-위험은 effects 표기에
이미 있는 원칙이라 새 조항이 아니다.
- 재수출 키워드를 reexport로 확정. export는 "처음 내보내기"와 흐려지는데
둘은 hash 규칙에서 의미가 다르고, use는 capability 전달 전용으로 남긴다.
- effects 절을 파라미터 목록 뒤·화살표 앞으로 이동. 중첩 함수 타입의 구문
모호성이 어순으로 사라지고 읽기 순서가 "입력과 권한 → 출력"이 된다.
선언과 타입 표기를 같은 어순으로 통일.
- effect 위치 제한을 검사기에서 구문 수준으로 격상. 파라미터 위치는 변수
단독 또는 리터럴 집합만, 합집합은 결과 위치 전용 프로덕션.
- scope는 이름을 갖는 형태로 확정. 중첩에서 바깥 스코프에 붙이는 정당한
패턴이 암묵 바인딩으로는 표현되지 않는다.
- capability struct를 표준 관용구로 문서화. named effect set과 scope
축약형은 v1 예약.
samples/는 명세가 아니라 탐침이다. 05는 통과하면 안 되는 파일이며 체커가
생기면 그대로 테스트 케이스가 된다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
관통하는 해법 하나로 다섯 항목을 닫는다 — second-class 값의 일반화.
- Capability 전달: use의 탈출을 capture 전면 금지가 아니라 "use의 전염"으로
해결한다. use 값을 capture한 closure는 그 자체가 use 값이 되고 use 파라미터
위치로만 전달된다. 저장하는 sink는 일반 fn을 요구하므로 대입이 거부되고,
판정 정보가 전부 시그니처에 남아 검사는 로컬로 유지된다. 별도의 nonescaping
개념을 만들지 않아 "한 개념 한 방식"이 지켜진다.
- Affinity 전이를 closure 환경까지 확장: callable은 fn(copyable)과
affine fn 두 종류이며, affinity는 타입 표기의 일부로 interface hash에
포함된다. bound method와 partial application도 같은 규칙.
- Effect 다형성: 합집합이 있는 이상 일반 unification이 아니므로 해소를
pattern unification으로 제한한다. 결정 위치에 단독 등장, 합집합은 결과
위치 전용이며 분해하지 않음. 역산 시그니처는 작성 단계에서 거부된다.
- Invalidation 순서 정정: 변경 모듈 재검사 → hash 비교 → 달라졌을 때만
dependents를 큐에 추가. hash 비교가 dependents 재검사보다 앞서야
"본문만 수정 시 downstream 0건"이 성립한다.
interface hash의 re-export 입력도 목록이 아니라 해소된 정의 본문으로 정정.
- 동시성: spawn은 primitive가 아니라 TaskScope capability의 메서드다.
TaskScope는 use-only라 탈출할 수 없고, join/cancel은 handle 소비 관례가
아니라 scope 구문의 실행 의미로 강제한다. v0가 미소비를 허용하는 이상
handle 기반 보장은 불가능하기 때문이다. 루트 TaskScope는 main에 주입.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
두 미해결 항목을 닫는다.
join 규칙 — 보수적 합집합으로 확정. 한 분기에서라도 moved면 병합 지점
이후 moved. 블록당 O(변수 수)로 결정적이고 오류가 한 줄로 끝난다.
경로별 정밀 추적은 오류 설명 가능성을 파는 거래라 채택하지 않고,
"분기 간 불일치 = 에러"는 정당한 코드까지 막아 과잉이다. 조건부 소비의
우회 강요는 인정하는 대가이며, 필요하면 v1에서 관용구로 흡수한다.
affine/linear — v0는 affine이다: 오용 금지, 누수 미검사. 따라서 v0
보안 정리의 범위는 non-duplication까지이며 자원 해제는 주장하지 않는다.
linear 검사와 해제 보장은 v1 과제로 미루되, 암묵적 drop은 선언되지 않은
effect를 실행해 철학 1과 충돌하므로 명시적 소비 강제 방향을 사전 베팅으로
남긴다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
문서(docs/thesis.md)에 네 가지 파생 결정을 반영:
- Effect 다형성: effect 변수는 타입 파라미터와 동일 규율(선언 명시,
해소는 호출 지점에서 로컬). 집합 의미론으로 row polymorphism 회피.
- 인터페이스 경계: public 경계 전면 명시, 본문 추론 누출 금지,
interface hash는 정규화된 시그니처 텍스트만으로 계산.
- 모듈/패키지: content hash는 무결성이지 가용성이 아님을 명시.
- 형식 명세 범위를 capability의 affine 규칙까지 확장.
스캐폴딩은 CLI 형태만 고정하고 파이프라인 단계는 비워 둔다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E