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
+3 -3
View File
@@ -75,7 +75,7 @@ type expr =
| E_try of { inner : expr; pos : pos }
| E_unary of { op : unop; operand : expr; pos : pos }
(* 복구 불가능한 실패. 타입은 Never — 돌아오지 않으므로 어떤 자리에도 놓인다 *)
| E_panic of { msg : expr; pos : pos }
| E_crash of { msg : expr; pos : pos }
| E_binary of { op : binop; lhs : expr; rhs : expr; pos : pos }
and closure = {
@@ -249,8 +249,8 @@ let binop_name = function
let rec buf_expr b = function
| E_lit (l, _) -> buf_lit b l
| E_panic { msg; _ } ->
Buffer.add_string b "(panic ";
| E_crash { msg; _ } ->
Buffer.add_string b "(crash ";
buf_expr b msg;
Buffer.add_char b ')'
| E_ident (n, _) -> Buffer.add_string b n
+1 -1
View File
@@ -320,7 +320,7 @@ let rec eval st (env : env) (e : Ir.t) : value =
| [ f ] -> apply st pos f []
| _ -> fail pos "spawn은 함수 하나를 받습니다")
| other -> fail pos (Printf.sprintf "%s에는 필드가 없습니다" (show other)))
| Ir.I_panic (msg, pos) -> (
| Ir.I_crash (msg, pos) -> (
match eval st env msg with VStr s -> fail pos s | v -> fail pos (show v))
| Ir.I_unary (op, e, pos) -> (
match (op, eval st env e) with
+2 -2
View File
@@ -45,7 +45,7 @@ type t =
| I_seq of stmt list * t (* 블록: 문 나열 + 꼬리 값 *)
| I_call of { callee : t; args : t list; pos : pos }
| I_field of { obj : t; name : string; pos : pos }
| I_panic of t * pos
| I_crash of t * pos
| I_unary of Ast.unop * t * pos
| I_binary of Ast.binop * t * t * pos
@@ -186,7 +186,7 @@ let rec lower_pat c (p : Ast.pattern) : pat =
let rec lower c (e : Ast.expr) : t =
match e with
| Ast.E_panic { msg; pos } -> I_panic (lower c msg, pos)
| Ast.E_crash { msg; pos } -> I_crash (lower c msg, pos)
| Ast.E_lit (l, _) -> I_lit l
| Ast.E_ident (n, pos) -> I_ref (classify c n, pos)
| Ast.E_list (xs, _) -> I_list (List.map (lower c) xs)
+2 -2
View File
@@ -187,8 +187,8 @@ type ctx = Borrow | Move of string (* 어디로 옮겨가는지 — 진단에
let rec walk st (ctx : ctx) (e : expr) : vinfo =
match e with
| E_lit _ -> v_copy
(* panic은 돌아오지 않지만 메시지 식은 계산된다. 메시지는 빌려 쓴다 *)
| E_panic { msg; _ } ->
(* crash는 돌아오지 않지만 메시지 식은 계산된다. 메시지는 빌려 쓴다 *)
| E_crash { msg; _ } ->
ignore (walk st Borrow msg);
v_copy
| E_ident (n, pos) -> (
+2 -2
View File
@@ -398,12 +398,12 @@ and parse_primary st =
| Token.Kw_fn -> parse_closure st
| Token.Kw_if -> parse_if st
| Token.Kw_match -> parse_match st
| Token.Kw_panic ->
| Token.Kw_crash ->
adv st;
expect st Token.LParen "(";
let msg = with_struct_ok st (fun () -> parse_expr st) in
expect_close st Token.RParen ")";
E_panic { msg; pos = p }
E_crash { msg; pos = p }
| Token.Kw_scope ->
adv st;
let name = ident st "새 scope 이름" in
+1 -1
View File
@@ -154,7 +154,7 @@ let rec resolve_pattern st seen = function
let rec resolve_expr st = function
| E_lit _ -> ()
| E_panic { msg; _ } -> resolve_expr st msg
| E_crash { msg; _ } -> resolve_expr st msg
| E_ident (n, pos) ->
if lookup_local st n = None then
if Hashtbl.mem st.items n then ()
+5 -5
View File
@@ -28,7 +28,7 @@ type kind =
| Kw_else
| Kw_match
| Kw_scope
| Kw_panic
| Kw_crash
| Kw_test
| Kw_true
| Kw_false
@@ -89,7 +89,7 @@ let keyword = function
| "else" -> Some Kw_else
| "match" -> Some Kw_match
| "scope" -> Some Kw_scope
| "panic" -> Some Kw_panic
| "crash" -> Some Kw_crash
| "test" -> Some Kw_test
| "true" -> Some Kw_true
| "false" -> Some Kw_false
@@ -119,7 +119,7 @@ let show_kind = function
| Kw_else -> "else"
| Kw_match -> "match"
| Kw_scope -> "scope"
| Kw_panic -> "panic"
| Kw_crash -> "crash"
| Kw_test -> "test"
| Kw_true -> "true"
| Kw_false -> "false"
@@ -184,8 +184,8 @@ let next_kind = function
| Kw_if -> Some Kw_else
| Kw_else -> Some Kw_match
| Kw_match -> Some Kw_scope
| Kw_scope -> Some Kw_panic
| Kw_panic -> Some Kw_test
| Kw_scope -> Some Kw_crash
| Kw_crash -> Some Kw_test
| Kw_test -> Some Kw_true
| Kw_true -> Some Kw_false
| Kw_false -> Some LParen
+3 -3
View File
@@ -164,11 +164,11 @@ let builtin_ctor = function
let rec infer env (e : expr) : T.t =
match e with
(* panic은 돌아오지 않는다. 타입은 Never이고 어떤 자리에도 놓인다 *)
| E_panic { msg; pos } ->
(* crash는 돌아오지 않는다. 타입은 Never이고 어떤 자리에도 놓인다 *)
| E_crash { msg; pos } ->
let t = infer env msg in
if not (T.unify t T.TString) then
mismatch env pos T.TString t "panic의 메시지";
mismatch env pos T.TString t "crash의 메시지";
T.TNever
| E_lit (L_int _, _) -> T.TInt
| E_lit (L_str _, _) -> T.TString
+2 -2
View File
@@ -10,7 +10,7 @@
type t =
| TUnknown
(* 값을 내지 않는 타입. panic의 타입이고 어떤 자리에도 놓일 수 있다.
(* 값을 내지 않는 타입. crash의 타입이고 어떤 자리에도 놓일 수 있다.
TUnknown과 다르다 — TUnknown은 "모른다"이고 TNever는 "돌아오지
않는다"이다. 둘 다 무엇과도 맞지만 생기는 이유가 다르다. *)
| TNever
@@ -141,7 +141,7 @@ let unify_eff a b =
let rec unify a b =
match (resolve a, resolve b) with
| TUnknown, _ | _, TUnknown -> true
(* Never는 어떤 타입 자리에도 놓인다. panic이 match 팔에 설 수 있는 이유다 *)
(* Never는 어떤 타입 자리에도 놓인다. crash가 match 팔에 설 수 있는 이유다 *)
| TNever, _ | _, TNever -> true
| TMeta r, TMeta r' when r == r' -> true
| TMeta r, t | t, TMeta r ->