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
+14 -14
View File
@@ -1523,7 +1523,7 @@ let () =
(* ------------------------------------------------------------------ *)
let () =
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_panictest" in
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_crashtest" in
ignore (Sys.command (Printf.sprintf "mkdir -p %s" (Filename.quote dir)));
let f = Filename.concat dir "m.cool" in
let run src =
@@ -1540,7 +1540,7 @@ let () =
pub fn name(e: E) -> String {\n\
\ match e {\n\
\ A => \"a\",\n\
\ B => panic(\"B는 아직\"),\n\
\ B => crash(\"B는 아직\"),\n\
\ }\n\
}\n\n\
test \"통과\" {\n\
@@ -1549,7 +1549,7 @@ let () =
test \"assert 실패\" {\n\
\ Test.assert(1 == 2)\n\
}\n\n\
test \"panic 실패\" {\n\
test \"crash 실패\" {\n\
\ Test.assert(name(B) == \"b\")\n\
}"
in
@@ -1558,9 +1558,9 @@ let () =
List.iter
(fun (e : Session.error) -> Printf.printf " (%s)\n" e.message)
es;
check "panic/test 예제가 검사를 통과한다" false
check "crash/test 예제가 검사를 통과한다" false
| Ok rs ->
check "panic/test 예제가 검사를 통과한다" true;
check "crash/test 예제가 검사를 통과한다" true;
check "테스트 셋이 잡힌다" (List.length rs = 3);
let failed =
List.filter (fun (r : Interp.test_result) -> r.t_failure <> None) rs
@@ -1576,10 +1576,10 @@ let () =
| Some (_, m) -> has_sub m "assert 실패"
| None -> false)
rs);
check "panic 메시지가 그대로 보고된다"
check "crash 메시지가 그대로 보고된다"
(List.exists
(fun (r : Interp.test_result) ->
r.t_name = "panic 실패"
r.t_name = "crash 실패"
&&
match r.t_failure with
| Some (_, m) -> has_sub m "B는 아직"
@@ -1594,7 +1594,7 @@ let () =
(* 테스트는 effect를 수행할 수 없다 — capability를 받지 않으므로 증명된다 *)
let () =
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_panictest" in
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_crashtest" in
let f = Filename.concat dir "e.cool" in
write f
"pub capability Db {\n\
@@ -1625,7 +1625,7 @@ let () =
check "samples/app의 테스트가 전부 통과한다"
(List.for_all (fun (r : Interp.test_result) -> r.t_failure = None) rs)
(* panic은 scope 트리를 타고 올라간다. 자식이 panic하면 그 scope는 실패하고,
(* crash는 scope 트리를 타고 올라간다. 자식이 crash하면 그 scope는 실패하고,
scope 뒤의 코드는 실행되지 않는다.
v0는 태스크가 하나라 이 규칙이 축약된 형태로만 관측된다. 아래에서 "자식
@@ -1640,16 +1640,16 @@ let () =
\ c.print(\"scope 앞\")\n\
\ scope work = root {\n\
\ work.spawn(fn() { c.print(\"자식 하나\") })\n\
\ work.spawn(fn() { panic(\"자식 둘이 죽었다\") })\n\
\ work.spawn(fn() { crash(\"자식 둘이 죽었다\") })\n\
\ }\n\
\ c.print(\"scope 뒤\")\n\
}")
with
| out, Some e ->
check "자식의 panic이 scope 밖으로 전파된다" (has_sub e.message "자식 둘이 죽었다");
check "panic 전의 출력은 남는다" (has_sub out "자식 하나");
check "자식의 crash가 scope 밖으로 전파된다" (has_sub e.message "자식 둘이 죽었다");
check "crash 전의 출력은 남는다" (has_sub out "자식 하나");
check "scope 뒤는 실행되지 않는다" (not (has_sub out "scope 뒤"))
| _, None ->
check "자식의 panic이 scope 밖으로 전파된다" false;
check "panic 전의 출력은 남는다" false;
check "자식의 crash가 scope 밖으로 전파된다" false;
check "crash 전의 출력은 남는다" false;
check "scope 뒤는 실행되지 않는다" false