naming: panic을 crash로 — 그리고 이름을 고르는 원칙을 철학에 넣는다

철학 6번을 추가했다:

  이름은 관례가 아니라 뜻에서 고른다 — 낯섦은 한 번 치르고 끝나지만
  부정확함은 읽는 사람마다 매번 치른다.

판정 방법도 같이 적었다. 그 단어로 평범한 문장을 써 보고, 단어가 문장을
도우면 맞는 이름이고 싸우면 틀린 이름이다.
  "크래시는 복구하는 것이 아니라 조사하는 것이다" — 돕는다
  "패닉은 복구할 수 없다" — 다른 언어에서는 할 수 있어 싸운다

panic의 자연어 뜻은 "갑작스러운 공포"다. 반응하는 쪽의 감정이지 결함에
대한 말이 아니다. 그리고 Go/Rust에서는 붙잡을 수 있어 이름이 거짓말을 한다.
crash는 "계획 없이 갑자기 완전히 망가져 끝남"이고 복구의 함의가 없다 —
크래시는 복구하는 게 아니라 조사하는 것이다.

어휘의 출신도 이유가 됐다. panic+recover는 Go 전통이고 거기엔 감독이 없다.
crash+supervision은 얼랭 전통이며, 우리가 만드는 것이 그쪽이다.

한국어 용어도 세 층으로 정리했다: 실패(Result) / 결함(crash) / 감독.
세 층이 세 가지 다른 기제로 규율된다 — 타입, 없음(발산), capability.
"상황이 나쁨"은 결함이 아니라 실패다. 이 선을 안 그으면 crash가 게으름의
배출구가 된다. "오류"는 컴파일러 진단에만 쓴다.

얼랭 질문에 대한 답도 기록했다: 감독은 가져오고 비구조적 spawn은 안
가져온다. sc.spawn과 sup.spawn(sc, f)로 갈리며 문법 변경이 없다 — 실패를
삼키려면 Supervisor를 받았어야 하고 그것이 시그니처에 보인다.

개명은 문법을 먼저 고치고 대조 장치로 확인했다. 문장 500개, 파일 26개
모두 갈림 0건.

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 18:21:05 +09:00
co-authored by Claude Opus 5
parent 5dc214baee
commit 2307bafda2
13 changed files with 97 additions and 55 deletions
+6 -6
View File
@@ -55,7 +55,7 @@
* "pub" | "fn" | "struct" | "enum" | "capability" | "const" |
* "import" | "as" | "reexport" | "let" | "mut" | "own" | "affine" |
* "copyable" | "effects" | "return" | "if" | "else" | "match" |
* "scope" | "panic" | "test" | "true" | "false"
* "scope" | "crash" | "test" | "true" | "false"
(* *)
*
* (, , , variant, )
@@ -244,13 +244,13 @@ primary<s> = literal
| match_expr
| scope_expr
| "(" , expr , ")"
| panic_expr
| crash_expr
| name_or_struct<s> ;
(* panic . Never,
(* crash . Never,
* Never match .
*
* effect . {Panic}
* effect . {Crash}
* effect . effect
* .
*
@@ -258,11 +258,11 @@ primary<s> = literal
* 1 .
* ( ).
*
* . panic
* . crash
* .
*
* : prelude import *)
panic_expr = "panic" , "(" , expr , ")" ;
crash_expr = "crash" , "(" , expr , ")" ;
(* ident struct . "{"
* *)
+54 -12
View File
@@ -28,6 +28,16 @@ AI의 코드 생성 속도 >> 신뢰 확보 속도.
→ "Effects: +payment.refund" 수준으로 리뷰 압축
5. 한 개념 = 한 방식: syntax variant 최소화, 공식 formatter 하나,
공식 toolchain 하나. 단순성의 기준은 "작성자"가 아니라 "리뷰어와 검증기"
6. 이름은 관례가 아니라 뜻에서 고른다 — 낯섦은 한 번 치르고 끝나지만
부정확함은 읽는 사람마다 매번 치른다
→ 판정: 그 단어로 그것이 무엇인지 평범한 문장을 써 보라. 단어가 문장을
도우면 맞는 이름, 문장과 싸우면 틀린 이름이다
"크래시는 복구하는 것이 아니라 조사하는 것이다" — 돕는다
"패닉은 복구할 수 없다" — 다른 언어에서는 할 수 있어 싸운다
→ 널리 쓰인다는 것은 옳다는 증거가 아니다. 운영체제의 "fault"가 그렇다 —
잘못이 아닌 것을 잘못이라 부르고, 그 오류까지 함께 전파된다
※ 관례가 정확하면 관례를 따른다. 관례라서가 아니라 정확해서다
※ 이름에만 적용된다. 동작을 관례와 다르게 만들 이유는 아니다
■ 파생 결정
@@ -338,29 +348,43 @@ L2 빠른 테스트 / L3 fuzzing / L4 formal proof → 요청 시, 분리 실행
invalidation 범위를 넓히는가? / unrelated code 의미를 바꾸는가? /
기존 개념의 중복 표현인가? → 강한 이유 없으면 거절
복구 불가능한 실패 — panic
panic(message) -> Never. 키워드다 (prelude가 없어 함수로 두면 매번 import).
- effect가 아니다. 경계 검사 하나에 {Panic}이 호출자 전부로 전염되면
실패의 세 층 — 실패 / 결함 / 감독
서로 다른 세 질문에 답한다. 아래 층은 위 층 없이도 성립한다.
실패 (Result) 호출자가 대처할 수 있는가? 타입이 강제한다
결함 (crash) 이 태스크가 계속할 수 있는가? 컴파일러는 추적하지 않는다
감독 죽은 태스크를 어떻게 다루는가? capability가 강제한다
세 층이 세 가지 다른 기제로 규율된다는 것이 제대로 나뉘었다는 증거다.
"상황이 나쁨"은 결함이 아니라 실패다 — 디스크가 가득 찬 것은 Result다.
이 선을 긋지 않으면 crash가 게으름의 배출구가 된다.
※ 취소는 이 셋 중 어디에도 없다. 취소는 실패가 아니라 "더 이상 필요 없음"
이며 별도의 결정으로 남는다.
"오류"는 컴파일러가 내는 진단에만 쓴다.
■ 결함 — crash
crash(message) -> Never. 키워드다 (prelude가 없어 함수로 두면 매번 import).
- effect가 아니다. 경계 검사 하나에 {Crash}가 호출자 전부로 전염되면
effect 절은 신호가 아니라 잡음이 된다. 발산이 effect가 아닌 것과 같은
이유다 — 무한 루프도 추적하지 않는다.
- Never는 어떤 타입 자리에도 놓인다. 그래야 match 팔에서 쓸 수 있고,
그게 없으면 panic은 식 자리에서 못 쓴다.
그게 없으면 crash는 식 자리에서 못 쓴다.
- 언어 수준 recover가 없다. 붙잡는 것이 있으면 그것은 예외이고, 예외는
시그니처에 안 적히므로 철학 1과 충돌한다.
- 되감기를 하지 않는다. ※ panic 시 자원 해제 여부는 자원 모델과 함께
- 되감기를 하지 않는다. ※ crash 시 자원 해제 여부는 자원 모델과 함께
결정한다 — 지금은 열어둔다.
- 0으로 나누기, assert 실패, 미래의 범위·오버플로가 전부 이 하나로 모인다.
panic은 프로그램의 버그를 말한다. 예상되는 실패는 Result다. 호출자가
대처할 수 있는 것을 panic으로 처리하면 오용이다.
crash는 프로그램의 결함을 말한다. 예상되는 실패는 Result다. 호출자가
대처할 수 있는 것을 crash로 처리하면 오용이다.
panic은 어디까지 올라가는가 (실패의 단위)
crash는 어디까지 올라가는가 (실패의 단위)
콜 스택의 root가 아니라 scope 트리의 root다. 즉 실패의 단위는 프로세스가
아니라 태스크다. scope가 이미 그 모양이기 때문이다 — 렉시컬이고, 블록을
나가는 것이 join이며, 자식이 죽었다는 사실이 부모에게 도달하는 지점이
문법에 이미 있다(닫는 중괄호).
panic은 scope 트리를 타고 올라간다. 자식이 panic하면 그 scope는 join
지점에서 panic한다. root까지 도달하면 실행이 끝난다.
crash는 scope 트리를 타고 올라간다. 자식이 crash하면 그 scope는 join
지점에서 crash한다. root까지 도달하면 실행이 끝난다.
v0는 태스크가 하나뿐이라 이 규칙이 관측되지 않는다 — "태스크가 죽는다"와
"프로그램이 죽는다"가 같은 사건이다. 그래도 지금 적는 이유는, 동시성이
@@ -369,9 +393,27 @@ v0는 태스크가 하나뿐이라 이 규칙이 관측되지 않는다 — "태
전파는 취소를 정하지 않고도 정의된다. 취소가 생기면 형제들이 언제 멈추는지가
바뀔 뿐, scope가 실패한다는 사실은 바뀌지 않는다.
※ 형제 취소는 여전히 미정이다. 대가를 알고 미룬다 — 취소가 없으면 형제
하나가 끝나지 않을 때 죽은 자식의 panic이 join에 도달하지 못한다.
하나가 끝나지 않을 때 죽은 자식의 crash가 join에 도달하지 못한다.
실패가 hang에 가려진다. 이것이 취소가 필요한 이유이기도 하다.
■ 얼랭 방식은 되는가 — 감독은 되고 비구조적 spawn은 안 된다
공유 가변 상태 없음, let it crash, 죽은 쪽의 상태를 물려받지 않음 —
얼랭과 이미 같다. 다른 경로로 같은 결론에 도착했다.
가져오지 않는 것: 얼랭의 비구조적 spawn(Pid가 부모보다 오래 산다).
부모 없는 태스크는 ambient authority와 같은 문제이고, 구조적 동시성을 고른
이유가 그것이다.
가져오는 것: 감독. scope 트리가 이미 supervision 트리 모양이다.
sc.spawn(f) 실패가 부모로 전파된다
sup.spawn(sc, f) 실패가 여기서 멈춘다
spawn은 하나의 개념이고 실패가 어디까지 가느냐가 권한으로 갈린다 — 실패를
삼키려면 Supervisor를 받았어야 하고 그것이 시그니처에 보인다. 문법 변경 없음.
재시작 가능한 자식은 copyable 클로저여야 한다(fn vs affine fn). 얼랭이 관례로
지키는 것을 affinity가 이미 검사한다. 재시작 전략은 라이브러리다(얼랭도 OTP).
※ 전제조건은 자원 모델이다. 죽은 태스크가 잡았던 것이 정리되지 않으면
재시작이 곧 누수다.
※ 얼랭의 보장은 BEAM의 선점 스케줄링과 프로세스별 힙 위에 선다. 우리에겐
아직 둘 다 없다 — 이것은 설계 층의 답이지 v0가 보여줄 수 있는 것이 아니다.
■ 격리 경계는 recover가 아니다
런타임은 격리 경계를 가질 수 있다. 테스트 러너가 첫 사례이고, 서버의
요청 경계가 두 번째가 될 것이다. 구분선은 이것이다:
@@ -397,7 +439,7 @@ capability를 만드는 문법도 없다. 따라서 effect-free임이 증명된
interface hash에 들어가지 않는다 — 테스트를 고쳤다고 downstream이
재검사되면 안 된다.
컴파일 타임 메타프로그래밍 없음. assert는 std/test.cool에 coollang으로
쓰인다 (panic 위의 설탕) — std에서 본문이 있는 첫 함수다. 실패 메시지에
쓰인다 (crash 위의 설탕) — std에서 본문이 있는 첫 함수다. 실패 메시지에
값이 안 나오는 것은 의도다. 표현식 텍스트를 잡으려면 매크로가 필요하다.
※ effect 있는 코드는 테스트할 수 없다. 가짜 capability를 만드는 수단이
없기 때문이다. 실제로 불편해진 뒤에 판단한다.