Files
coolguyandClaude Opus 5 2307bafda2 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
2026-08-30 18:21:05 +09:00

189 lines
6.2 KiB
OCaml

(* 타입과 effect 표현, 그리고 지역 unification.
TUnknown이 핵심이다. 외부 모듈에서 오는 이름은 모듈 로딩이 없는 v0에서
해소할 수 없다. 그런 타입은 TUnknown이 되고 무엇과도 맞는다 — 모르는 것을
틀렸다고 말하지 않기 위해서다.
effect는 순서 없는 집합이고 합성은 합집합이다. 변수는 집합 변수이며,
호출 지점에서 결정 위치(파라미터의 effect 자리에 단독으로 선 변수)를 통해
메타에 묶인다. 함수 하나 범위를 넘지 않는다. *)
type t =
| TUnknown
(* 값을 내지 않는 타입. crash의 타입이고 어떤 자리에도 놓일 수 있다.
TUnknown과 다르다 — TUnknown은 "모른다"이고 TNever는 "돌아오지
않는다"이다. 둘 다 무엇과도 맞지만 생기는 이유가 다르다. *)
| TNever
| TInt
| TBool
| TString
| TUnit
| TVar of string
| TCon of string * t list
| TFn of { affine : bool; params : t list; eff : eff; ret : t }
| TMeta of meta ref
and meta = Unbound of int | Bound of t
(* effect 집합. 원소는 구체 이름, 집합 변수, 또는 호출 지점의 미지수다. *)
and eff = atom list
and atom =
| A_name of string * string (* Cap.method *)
| A_var of string
| A_meta of emeta ref
and emeta = EUnbound of int | EBound of eff
let counter = ref 0
let fresh () =
incr counter;
TMeta (ref (Unbound !counter))
let fresh_eff () =
incr counter;
[ A_meta (ref (EUnbound !counter)) ]
let rec resolve t =
match t with TMeta { contents = Bound u } -> resolve u | _ -> t
(* 묶인 메타를 펼치고 중복을 없앤다. 집합이므로 순서는 의미가 없다. *)
let rec eff_resolve (e : eff) : eff =
let expand a =
match a with
| A_meta { contents = EBound inner } -> eff_resolve inner
| _ -> [ a ]
in
let flat = List.concat_map expand e in
let mem a acc =
List.exists
(fun b ->
match (a, b) with
| A_name (c1, m1), A_name (c2, m2) -> c1 = c2 && m1 = m2
| A_var x, A_var y -> x = y
| A_meta r, A_meta r' -> r == r'
| _ -> false)
acc
in
List.fold_left (fun acc a -> if mem a acc then acc else a :: acc) [] flat
|> List.rev
let atom_show = function
| A_name (c, m) -> c ^ "." ^ m
| A_var v -> v
| A_meta { contents = EUnbound n } -> Printf.sprintf "_e%d" n
| A_meta { contents = EBound _ } -> "?"
let eff_show e =
match eff_resolve e with
| [] -> "{}"
| atoms -> "{" ^ String.concat ", " (List.map atom_show atoms) ^ "}"
let atom_eq a b =
match (a, b) with
| A_name (c1, m1), A_name (c2, m2) -> c1 = c2 && m1 = m2
| A_var x, A_var y -> x = y
| A_meta r, A_meta r' -> r == r'
| _ -> false
(* declared가 덮지 못하는 원소들. 미지수는 판정을 미룬다 —
결정되지 않은 것을 위반이라고 말하지 않는다. *)
let eff_missing ~declared ~performed =
let declared = eff_resolve declared in
List.filter
(fun a ->
match a with
| A_meta { contents = EUnbound _ } -> false
| _ -> not (List.exists (atom_eq a) declared))
(eff_resolve performed)
let rec show t =
match resolve t with
| TUnknown -> "?"
| TNever -> "Never"
| TInt -> "Int"
| TBool -> "Bool"
| TString -> "String"
| TUnit -> "Unit"
| TVar v -> v
| TCon (n, []) -> n
| TCon (n, args) -> n ^ "[" ^ String.concat ", " (List.map show args) ^ "]"
| TFn { affine; params; eff; ret } -> (
(if affine then "affine fn(" else "fn(")
^ String.concat ", " (List.map show params)
^ ")"
^ (match eff_resolve eff with [] -> "" | e -> " effects " ^ eff_show e)
^ match resolve ret with TUnit -> "" | r -> " -> " ^ show r)
| TMeta { contents = Unbound n } -> Printf.sprintf "_%d" n
| TMeta { contents = Bound _ } -> "?"
let rec occurs r t =
match resolve t with
| TMeta r' -> r == r'
| TCon (_, args) -> List.exists (occurs r) args
| TFn { params; ret; _ } -> List.exists (occurs r) params || occurs r ret
| _ -> false
(* 결정 위치의 해소: 파라미터의 effect 자리에 단독으로 선 미지수만 묶는다.
그 외에는 참을 돌려주고, 실제 포함 검사는 호출 지점에서 방향을 아는
쪽이 한다 (진단 품질 때문에). *)
let unify_eff a b =
match (eff_resolve a, eff_resolve b) with
| [ A_meta ({ contents = EUnbound _ } as r) ], other ->
r := EBound other;
true
| other, [ A_meta ({ contents = EUnbound _ } as r) ] ->
r := EBound other;
true
| _ -> true
let rec unify a b =
match (resolve a, resolve b) with
| TUnknown, _ | _, TUnknown -> true
(* Never는 어떤 타입 자리에도 놓인다. crash가 match 팔에 설 수 있는 이유다 *)
| TNever, _ | _, TNever -> true
| TMeta r, TMeta r' when r == r' -> true
| TMeta r, t | t, TMeta r ->
if occurs r t then false
else (
r := Bound t;
true)
| TInt, TInt | TBool, TBool | TString, TString | TUnit, TUnit -> true
| TVar x, TVar y -> x = y
| TCon (n, xs), TCon (m, ys) ->
n = m && List.length xs = List.length ys && List.for_all2 unify xs ys
| TFn f, TFn g ->
(* affinity는 타입 동등성의 일부가 아니다. 값이 affine인지는 무엇을
capture했는지로 정해지는 substructural 성질이고, move/affinity 검사가
소유한다. 여기서 섞으면 두 검사가 서로의 결론을 앞질러 버린다. *)
List.length f.params = List.length g.params
&& List.for_all2 unify f.params g.params
&& unify_eff f.eff g.eff && unify f.ret g.ret
| _ -> false
(* 제네릭 인스턴스화: 타입 변수와 effect 변수를 동시에 바꾼다 *)
let rec subst tenv eenv t =
match resolve t with
| TVar v -> ( match List.assoc_opt v tenv with Some u -> u | None -> TVar v)
| TCon (n, args) -> TCon (n, List.map (subst tenv eenv) args)
| TFn f ->
TFn
{
affine = f.affine;
params = List.map (subst tenv eenv) f.params;
eff = subst_eff eenv f.eff;
ret = subst tenv eenv f.ret;
}
| u -> u
and subst_eff eenv (e : eff) : eff =
eff_resolve
(List.concat_map
(fun a ->
match a with
| A_var v -> (
match List.assoc_opt v eenv with Some s -> s | None -> [ a ])
| _ -> [ a ])
e)