thesis+samples: 표기 결정 6건 반영과 문법 탐침 추가

샘플을 먼저 써서 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
This commit is contained in:
2026-08-30 01:35:30 +09:00
co-authored by Claude Opus 5
parent c073e18b41
commit e3b19b1300
9 changed files with 431 additions and 4 deletions
+40 -4
View File
@@ -66,6 +66,20 @@ Capability 전달 (Alias/Move 모델의 제3의 규칙):
죽이므로 기각. 별도의 nonescaping 개념을 만들지 않고 use의 전염으로 정의해 죽이므로 기각. 별도의 nonescaping 개념을 만들지 않고 use의 전염으로 정의해
"한 개념 한 방식"을 지킨다 (Swift @noescape, second-class values의 검증된 경로) "한 개념 한 방식"을 지킨다 (Swift @noescape, second-class values의 검증된 경로)
- 소유권 이전이 필요할 때만 명시적 move - 소유권 이전이 필요할 때만 명시적 move
- 파라미터 위치의 callable은 기본이 use(비탈출)이고, 저장하는 쪽이 own fn으로
유표기한다. 파라미터 밖(반환 타입, struct 필드, channel 원소)의 callable은
use가 애초에 불가능한 위치이므로 무표기로 항상 owned 의미다
※ 무표기-흔함 / 유표기-위험은 이미 언어의 다른 곳에 있는 규칙이다
(effect 없는 함수가 무표기, effect 있는 쪽이 effects 유표기).
새 조항이 아니라 같은 원칙의 적용이다
※ 반대로 두면 클로저를 저장하지 않는 고차 함수 — 즉 표준 라이브러리의 거의 전부 —
가 use 표기로 도배된다. 위험한 쪽(클로저를 오래 보관하는 sink)에 표기가 가야
리뷰어의 눈이 그리로 간다
- capability가 셋 이상 필요하면 capability struct로 접는다 (표준 관용구):
struct Deps { pay: PaymentGateway, log: Logger, db: Database }
use deps: Deps로 받으면 affinity 전이와 use 규칙이 그대로 적용되므로
새 언어 기능 없이 시그니처가 접힌다
※ capability 3개 이상은 대체로 응집도가 낮다는 설계 신호이기도 하다
근거: non-duplication은 "복제 금지"이지 "재사용 금지"가 아니다. use는 복제를 근거: non-duplication은 "복제 금지"이지 "재사용 금지"가 아니다. use는 복제를
만들지 않으므로 보안 정리와 양립하고, 탈출 금지가 locality를 보존한다. 만들지 않으므로 보안 정리와 양립하고, 탈출 금지가 locality를 보존한다.
@@ -102,14 +116,25 @@ Affinity 전이 (보안 주장의 필수 전제):
※ v0는 affine 미소비(누수)를 허용하므로 handle 기반 보장은 애초에 불가능하다. ※ v0는 affine 미소비(누수)를 허용하므로 handle 기반 보장은 애초에 불가능하다.
실행 의미로 옮겨야 그 허점이 사라진다 실행 의미로 옮겨야 그 허점이 사라진다
- 루트 TaskScope는 main에 주입 → ambient authority 없이 권한 사슬이 main부터 닫힌다 - 루트 TaskScope는 main에 주입 → ambient authority 없이 권한 사슬이 main부터 닫힌다
- scope는 이름을 갖는다: scope sc { sc.spawn(...) }. 암묵 바인딩은 채택하지 않는다
※ 중첩 시 결정적이다. scope outer { scope inner { ... } }에서 안쪽 블록이
바깥 스코프에 spawn하는 것(수명이 다른 태스크를 의도적으로 부모에 붙이는
정당한 패턴)이 암묵 바인딩으로는 표현 불가이거나 섀도잉 규칙이라는
새 복잡성을 부른다. 이름이 있으면 outer.spawn / inner.spawn으로 그냥 갈린다
※ "어느 스코프에 붙는 태스크인가"가 리뷰어 눈에 보이는 것 자체가 이 언어가
파는 물건이다. 축약형은 체감 불만이 실제로 쌓이면 v1에서 검토
Effect 다형성 (철학 2,5에서 파생): Effect 다형성 (철학 2,5에서 파생):
- effect 변수는 타입 파라미터와 정확히 같은 규율을 따른다: - effect 변수는 타입 파라미터와 정확히 같은 규율을 따른다:
시그니처에 선언은 명시 강제, 해소는 호출 지점에서 로컬 unification 시그니처에 선언은 명시 강제, 해소는 호출 지점에서 로컬 unification
fn map<a, b, e: effects>( fn map<a, b, e: effects>(
f: fn(a) -> b effects e, xs: List<a>,
xs: List<a> f: fn(a) effects e -> b,
) -> List<b> effects e ) effects e -> List<b>
※ effects 절은 파라미터 목록 뒤, 화살표 앞이다. 함수 선언과 함수 타입이 같은
어순을 쓴다. 반환 타입 뒤에 두면 중첩 함수 타입에서 어느 쪽 effects인지
구문만으로 갈리지 않는다 — 괄호로 떠넘기지 않고 어순으로 없앤다.
읽기 순서도 "입력과 권한 → 출력"이 되어 자연스럽다
※ 전역 추론이 배제하는 것은 "선언 없이 프로그램 전체를 보고 알아내기"이지 ※ 전역 추론이 배제하는 것은 "선언 없이 프로그램 전체를 보고 알아내기"이지
선언된 변수의 로컬 해소가 아니다. map 체크에 필요한 것은 선언된 변수의 로컬 해소가 아니다. map 체크에 필요한 것은
map과 인자의 시그니처뿐 (locality 유지) map과 인자의 시그니처뿐 (locality 유지)
@@ -120,6 +145,11 @@ Effect 다형성 (철학 2,5에서 파생):
· 변수의 해는 그 결정 위치에서 읽어오기만 한다 · 변수의 해는 그 결정 위치에서 읽어오기만 한다
· 합집합 식은 결과 위치에만 허용하며 절대 분해하지 않는다 · 합집합 식은 결과 위치에만 허용하며 절대 분해하지 않는다
· 합집합 식이 기대 타입과 대조되는 위치는 compile error + 명시적 인스턴스화 요구 · 합집합 식이 기대 타입과 대조되는 위치는 compile error + 명시적 인스턴스화 요구
※ 이 제한은 검사기가 아니라 구문 수준에 새긴다: 파라미터 위치의 effect 절
프로덕션은 effects <변수> 또는 effects { 리터럴 집합 }만 허용하고,
합집합 프로덕션은 결과 위치 전용이다. "검사기가 거부"가 아니라
"그런 문장이 존재하지 않음"이 되므로 오류가 파서의 한 줄로 끝나고,
형식 명세에서 증명할 대상도 그만큼 줄어든다
※ map·filter·fold류는 전부 이 형태에 들어간다. 역산이 필요한 시그니처(peel류)는 ※ map·filter·fold류는 전부 이 형태에 들어간다. 역산이 필요한 시그니처(peel류)는
작성 자체가 거부된다 — 표현력을 팔아 결정성을 사는 거래이고, 작성 자체가 거부된다 — 표현력을 팔아 결정성을 사는 거래이고,
이 언어에서는 항상 그 방향이 맞다 이 언어에서는 항상 그 방향이 맞다
@@ -146,7 +176,10 @@ interface hash의 입력 = 모듈 exported surface 전체의 의미적 정규형
- 타입의 affinity (copyable / affine) - 타입의 affinity (copyable / affine)
- exported constant의 타입과 값 - exported constant의 타입과 값
- capability/effect 선언 - capability/effect 선언
- re-export된 선언을 완전히 해소한 정의 본문 (목록이 아니다) - reexport된 선언을 완전히 해소한 정의 본문 (목록이 아니다)
※ 키워드는 reexport 단독이다. export는 "처음 내보내기"와 "다시 내보내기"를
흐리는데 이 둘은 hash 규칙에서 의미가 다르므로 구문에서도 달라야 하고,
use는 capability 전달 전용으로 보존한다
※ A의 enum에 variant가 추가되면 B의 소스가 그대로여도 B의 hash가 변하고, ※ A의 enum에 variant가 추가되면 B의 소스가 그대로여도 B의 hash가 변하고,
C의 exhaustive match가 재검사된다. "의미적 정규형"을 구현 수준까지 내린 것 C의 exhaustive match가 재검사된다. "의미적 정규형"을 구현 수준까지 내린 것
원칙: downstream 검사 결과에 영향을 줄 수 있는 모든 것을 포함한다. 의심스러우면 원칙: downstream 검사 결과에 영향을 줄 수 있는 모든 것을 포함한다. 의심스러우면
@@ -230,6 +263,9 @@ Generics (철학 2에서 파생):
- 독립 verifier: compiler를 통짜로 신뢰하지 않고 IR invariants 재검증 - 독립 verifier: compiler를 통짜로 신뢰하지 않고 IR invariants 재검증
- semantic diff, IR 기반 semantic operations (AI 툴링) - semantic diff, IR 기반 semantic operations (AI 툴링)
- generics 완성, contracts, 공유 mutable 탈출구(명시적 타입 하나) - generics 완성, contracts, 공유 mutable 탈출구(명시적 타입 하나)
- named effect set (effects Checkout = {PaymentGateway.charge, Database.write}).
v0는 풀어쓴다 — 무엇을 접어도 되는지는 리뷰 가능성 데이터가 쌓인 뒤에 안다
- 단일 스코프 함수 한정 scope 축약형 (체감 불만이 실제로 쌓였을 때만)
- 제한적 second-class borrow (Alias/Move 모델의 "반환으로 돌려받기" 대가 완화) - 제한적 second-class borrow (Alias/Move 모델의 "반환으로 돌려받기" 대가 완화)
- linear 검사(자원 해제 보장). 사전 베팅: 암묵적 drop보다 명시적 close(f) 강제 + - linear 검사(자원 해제 보장). 사전 베팅: 암묵적 drop보다 명시적 close(f) 강제 +
미소비 시 compile error(순수 linear) 쪽이 coollang 철학에 맞는다 — effect가 미소비 시 compile error(순수 linear) 쪽이 coollang 철학에 맞는다 — effect가
+53
View File
@@ -0,0 +1,53 @@
// 01. use 파라미터 + effects 절이 한 시그니처에 동시에 붙는 모양
//
// 어순: fn 이름(파라미터) effects {...} -> 반환타입
// 검증 대상: capability를 받는 평범한 함수가 사람이 읽을 만한가.
import "cool.dev/std/list" as List
pub fn refund_order(
use pay: PaymentGateway,
id: OrderId,
) effects {PaymentGateway.refund} -> Result<Receipt, PayError> {
pay.refund(id)
}
// capability 둘, effect 둘.
pub fn refund_and_log(
use pay: PaymentGateway,
use log: Logger,
id: OrderId,
) effects {PaymentGateway.refund, Logger.write} -> Result<Receipt, PayError> {
let receipt = pay.refund(id)?
log.write("refunded: ", id)
Ok(receipt)
}
// capability 셋 이상은 struct로 접는다 (표준 관용구).
// Deps는 affine 필드를 가지므로 전이적으로 affine이고, use로 빌려 쓴다.
pub struct Deps {
pay: PaymentGateway,
log: Logger,
db: Database,
}
pub fn checkout(
use deps: Deps,
order: Order,
) effects {PaymentGateway.charge, Database.write, Logger.write} -> Result<Receipt, Error> {
let receipt = deps.pay.charge(order.id, order.amount)?
deps.db.write(receipt)?
deps.log.write("checkout: ", order.id)
Ok(receipt)
}
// use 값을 다른 use 파라미터로 넘기는 것은 허용된다 (복제가 아니다).
pub fn refund_all(
use pay: PaymentGateway,
ids: List<OrderId>,
) effects {PaymentGateway.refund} -> Result<Unit, PayError> {
List.each(ids, fn(id) {
refund_order(pay, id)?
Ok(unit)
})
}
+46
View File
@@ -0,0 +1,46 @@
// 02. effect 변수와 구문 수준 제한
//
// 파라미터 위치의 callable은 기본이 use(비탈출)이므로 표기가 없다.
// 파라미터 위치의 effect 절은 `effects <변수>` 또는 `effects {리터럴}`만 가능하고,
// 합집합은 결과 위치에서만 쓸 수 있다 — 문법이 그렇게 생겼다.
pub fn map<a, b, e: effects>(
xs: List<a>,
f: fn(a) effects e -> b,
) effects e -> List<b>
pub fn each<a, e: effects>(
xs: List<a>,
f: fn(a) effects e,
) effects e
pub fn fold<a, acc, e: effects>(
xs: List<a>,
init: acc,
f: fn(acc, a) effects e -> acc,
) effects e -> acc
// 결과 위치의 합집합. 분해되지 않으므로 결정적이다.
pub fn map_then_each<a, b, e1: effects, e2: effects>(
xs: List<a>,
f: fn(a) effects e1 -> b,
g: fn(b) effects e2,
) effects e1 | e2 {
each(map(xs, f), g)
}
// 클로저를 저장하는 sink는 own으로 유표기한다. 여기엔 use 값을 넘길 수 없다.
pub fn register(handler: own fn()) effects {Registry.add}
// 호출 지점: e는 인자의 시그니처에서 읽어온다. 추론이 아니라 읽기.
pub fn total_size(
use fs: FileSystem,
paths: List<Path>,
) effects {FileSystem.stat} -> Int {
fold(paths, 0, fn(acc, p) { acc + fs.stat(p).size })
}
// effect-free 클로저는 effects 절을 생략한다 (기본값 {}, 추론 아님).
pub fn lengths(xs: List<String>) -> List<Int> {
map(xs, fn(s) { String.len(s) })
}
+54
View File
@@ -0,0 +1,54 @@
// 03. TaskScope capability와 이름 있는 scope
//
// scope는 이름을 갖는다. 중첩 시 어느 스코프에 붙는 태스크인지가 코드에 보인다.
pub fn main(
use sc: TaskScope,
use fs: FileSystem,
use log: Logger,
) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
let paths = [Path("a.txt"), Path("b.txt"), Path("c.txt")]
// 블록 종료 시 런타임이 자식 전원을 join한다. handle 소비에 의존하지 않는다.
scope sc {
List.each(paths, fn(p) {
sc.spawn(fn() {
let body = fs.read(p)
log.write("read: ", p)
})
})
}
log.write("all done")
}
// 중첩. 이름이 있으므로 outer.spawn과 inner.spawn이 그냥 갈린다.
// 안쪽 블록에서 바깥 스코프에 붙이는 것도 표현 가능하다 — 수명이 코드에 보인다.
pub fn fan_out(
use outer: TaskScope,
use fs: FileSystem,
use log: Logger,
groups: List<List<Path>>,
) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
List.each(groups, fn(g) {
scope inner {
List.each(g, fn(p) {
inner.spawn(fn() { fs.read(p) })
})
// 이 태스크는 inner가 아니라 outer의 수명을 따른다.
outer.spawn(fn() { log.write("group done") })
}
})
}
// spawn 클로저는 by-move 또는 immutable capture만 가능하다.
pub fn broadcast(
use sc: TaskScope,
use log: Logger,
msg: String,
) effects {TaskScope.spawn, Logger.write} {
scope sc {
sc.spawn(fn() { log.write(msg) })
sc.spawn(fn() { log.write(msg) })
}
}
+36
View File
@@ -0,0 +1,36 @@
// 04. enum 정의 본문이 interface surface에 들어가는 이유
//
// variant 추가가 downstream exhaustive match를 깨뜨리는 경로.
pub enum PayError {
Declined(Code),
Network(Retryable),
Fraud,
}
pub copyable struct Receipt {
id: ReceiptId,
amount: Money,
}
// exhaustive match. PayError에 variant가 추가되면 여기가 compile error가 된다.
pub fn is_retryable(e: PayError) -> Bool {
match e {
Declined(_) => false,
Network(r) => r.retryable,
Fraud => false,
}
}
pub fn describe(e: PayError) -> String {
match e {
Declined(code) => String.concat("declined: ", Code.show(code)),
Network(_) => "network",
Fraud => "fraud",
}
}
// 재수출. hash 입력은 이름 목록이 아니라 해소된 정의 본문이다.
// 전용 키워드를 쓴다 — "처음 내보내기"와 의미가 다르고, hash 전파를 동반한다.
reexport PayError
reexport Receipt
+81
View File
@@ -0,0 +1,81 @@
// 05. 컴파일 에러가 나야 하는 코드
//
// 각 함수는 주석에 적힌 진단 하나를 정확히 내야 한다.
// 파서·체커가 생기면 그대로 테스트 케이스가 된다.
// [E-move-after-move] affine 값의 이중 소비
pub fn double_close(f: File) effects {File.close} {
File.close(f)
File.close(f) // ERROR: f는 이미 move됨 (앞줄에서 소비)
}
// [E-move-join] 분기 병합은 보수적 합집합
pub fn conditional_close(f: File, c: Bool) effects {File.close} {
if c {
File.close(f)
}
File.close(f) // ERROR: f는 이 분기에서 move됨 (조건부 소비)
}
// 정당한 형태 — 양쪽 분기에서 소비하면 통과해야 한다.
pub fn both_branches_close(f: File, c: Bool) effects {File.close} {
if c {
File.close(f)
} else {
File.close(f)
}
}
// [E-use-escape] use 값의 반환
pub fn leak_capability(use pay: PaymentGateway) -> PaymentGateway {
pay // ERROR: use 값은 반환할 수 없음
}
// [E-use-escape] use 값의 저장
pub struct Holder {
pay: PaymentGateway,
}
pub fn store_capability(use pay: PaymentGateway) -> Holder {
Holder { pay: pay } // ERROR: use 값은 struct에 저장할 수 없음
}
// [E-use-escape] use-closure를 own fn 위치에 전달
pub fn register(handler: own fn()) effects {Registry.add}
pub fn escape_via_closure(use pay: PaymentGateway) effects {Registry.add} {
register(fn() { pay.refund(OrderId(1)) })
// ERROR: pay를 capture한 클로저는 use 값이며 own fn 위치에 전달할 수 없음
}
// [E-affinity-transitive] affine 필드를 가진 타입을 copyable로 선언
pub copyable struct Box {
f: File, // ERROR: affine 필드(File)와 copyable 선언은 공존할 수 없음
}
// [E-callable-affinity] affine 값을 capture한 클로저를 fn 위치에 대입
// 반환 타입 위치의 callable은 무표기 owned이지만, affinity는 별개로 표기된다.
pub fn misuse_affine_closure(f: File) -> fn() effects {File.close} {
fn() { File.close(f) }
// ERROR: f를 capture했으므로 타입은 affine fn()이며 fn() 위치에 대입할 수 없음
}
// [E-spawn-capture] spawn 클로저의 mutable capture
pub fn spawn_mutable(use sc: TaskScope, mut counter: Int) effects {TaskScope.spawn} {
scope sc {
sc.spawn(fn() { counter = counter + 1 })
// ERROR: spawn 클로저는 mutable 참조를 capture할 수 없음
}
}
// [E-effect-undeclared] 선언되지 않은 effect
pub fn silent_write(use log: Logger) {
log.write("hi") // ERROR: effect Logger.write가 시그니처에 선언되지 않음
}
// [E-syntax-effect-union] 파라미터 위치의 합집합은 문법에 존재하지 않는다.
// 검사기가 거부하는 것이 아니라 파서가 거부한다.
pub fn peel<a, e: effects>(
f: fn(a) effects e | {Logger.write},
) effects e -> fn(a)
// ERROR (parse): 파라미터 위치의 effects 절은 변수 단독 또는 리터럴 집합만 허용
+37
View File
@@ -0,0 +1,37 @@
// 06. callable affinity — fn과 affine fn의 구분
//
// affinity(타입 속성)와 use/own(파라미터 위치 속성)은 직교한다.
// copyable 환경만 capture → fn. 복사해서 여러 번 써도 된다.
pub fn make_formatter(prefix: String) -> fn(String) -> String {
fn(s) { String.concat(prefix, s) }
}
// affine 값을 소유 → affine fn. 자신도 move 규칙을 따른다.
pub fn deferred_close(f: File) -> affine fn() effects {File.close} {
fn() { File.close(f) }
}
// 파라미터 위치이므로 use가 기본이지만, affine fn은 소유해야 호출 후 소비된다.
pub fn run_once(action: own affine fn() effects {File.close}) effects {File.close} {
action()
}
pub fn example(f: File) effects {File.close} {
let close = deferred_close(f)
run_once(close)
// run_once(close) // ERROR가 되어야 함: close는 affine, 이미 move됨
}
// use 클로저 — 표기가 없는 쪽이 흔한 쪽이다.
pub fn with_retry<a, e: effects>(
times: Int,
body: fn() effects e -> Result<a, Error>,
) effects e -> Result<a, Error>
pub fn refund_with_retry(
use pay: PaymentGateway,
id: OrderId,
) effects {PaymentGateway.refund} -> Result<Receipt, Error> {
with_retry(3, fn() { pay.refund(id) })
}
+40
View File
@@ -0,0 +1,40 @@
// 07. exported surface만 모은 파일 — interface artifact가 담아야 할 것
//
// 이 파일에 보이는 것 전부가 interface hash 입력이고,
// downstream이 검사에 쓰는 정보 전부다. 본문은 하나도 필요 없다.
pub capability PaymentGateway {
fn refund(id: OrderId) effects {PaymentGateway.refund} -> Result<Receipt, PayError>
fn charge(id: OrderId, amount: Money)
effects {PaymentGateway.charge} -> Result<Receipt, PayError>
}
// 타입 정의 본문 (exhaustiveness에 직결하므로 variant까지 전부)
pub enum PayError {
Declined(Code),
Network(Retryable),
Fraud,
}
// affinity는 유도되지만 표기된다: 필드에 affine이 없으므로 copyable
pub copyable struct Receipt {
id: ReceiptId,
amount: Money,
}
// File을 필드로 가지므로 전이적으로 affine (선언하지 않아도 유도됨)
pub struct AuditLog {
sink: File,
name: String,
}
// exported constant는 타입과 값이 모두 hash 입력
pub const MAX_RETRIES: Int = 3
// 함수는 시그니처만. 본문은 hash에 들어가지 않는다.
pub fn refund_order(
use pay: PaymentGateway,
id: OrderId,
) effects {PaymentGateway.refund} -> Result<Receipt, PayError>
pub fn retry_budget() -> Int
+44
View File
@@ -0,0 +1,44 @@
# samples — 문법 탐침
여기 있는 `.cool` 파일은 **명세가 아니라 탐침**이다. 목적은 하나:
`docs/thesis.md`의 v0 성공 기준 중 "체감" 항목 — 확정된 규칙(use/own, affine,
effect 변수, TaskScope)으로 짠 코드가 사람이 읽을 만한지 — 을 EBNF를 쓰기 전에
눈으로 확인하는 것.
파서는 아직 없으므로 이 파일들은 컴파일되지 않는다.
| 파일 | 검증 대상 |
|---|---|
| 01_capability_signature | use 파라미터 + effects 절, capability struct 관용구 |
| 02_higher_order_effects | effect 변수, 구문 수준 제한, use 기본 / own 유표기 |
| 03_scope_concurrency | TaskScope, 이름 있는 scope, 중첩 시 수명 표현 |
| 04_enum_match_interface | enum 정의 본문이 interface surface에 들어가는 경로 |
| 05_move_errors | **에러가 나야 하는** 코드 — 진단 하나씩 |
| 06_affine_closure | callable affinity (fn vs affine fn), use/own과의 직교성 |
| 07_module_interface | interface artifact가 담아야 할 것 전부 |
`05_move_errors.cool`은 통과하면 안 되는 파일이다. 각 함수 주석의 `[E-...]`
태그가 기대 진단이며, 체커가 생기면 그대로 테스트 케이스가 된다.
## 확정된 표기
- 어순: `fn 이름(파라미터) effects {...} -> 반환타입`.
함수 타입도 동일: `fn(a) effects e -> b`
- 모듈: 파일 = 모듈, `pub`이 exported, `import "domain/path" as Name`
- 재수출: `reexport Name` (전용 키워드 — hash 전파를 동반하므로 구분한다)
- capability 파라미터: `fn f(use pay: Gateway)`
- callable 파라미터: 기본이 use(비탈출, 무표기). 저장하는 쪽만 `own fn`
- 파라미터 밖(반환 타입, struct 필드, channel 원소)의 callable: 무표기 owned
- affine 클로저 타입: `affine fn(a) effects e -> b`
- effect 집합: `effects {Type.method, ...}`, 빈 집합은 생략
- effect 변수: `<e: effects>` 선언, 합집합 `e1 | e2`는 결과 위치 전용
- 제네릭: `List<a>`
- scope: `scope sc { ... }` — 이름 필수, 블록 종료 시 join/cancel
- 오류 전파: `?`
## 아직 열려 있는 것
- 표현식 `a < b`와 제네릭 `List<a>``<` 모호성 해소 방법
- `?`의 정확한 의미론 (Result 전용인지, 어디까지 전파하는지)
- `match` 가드, 중첩 패턴의 범위
- 리터럴 배열 `[...]`과 컬렉션 표기