effects: effect/capability 검사
문서가 "사활"이라고 지목한 단계다. effects 절이 여기서부터 장식이 아니라 검사 대상이 된다. 핵심 규칙 — 미선언 effect = compile error(철학 1). 수행한 자리를 들고 다니므로 진단이 함수 머리가 아니라 실제로 수행한 줄에 붙는다. effect가 흐르는 경로 넷을 모두 막았다: - capability 메서드 호출이 그 메서드의 선언된 effect를 요구한다 - 함수 호출이 그 함수의 effect를 물려준다 - 클로저의 effect는 정의한 자리가 아니라 부르는 자리에서 일어난다. 클로저를 만들기만 하는 것은 effect가 아니고, 인자로 넘겨 호출되는 순간 호출자의 것이 된다 - 파라미터가 허용한 범위를 넘는 함수를 넘기면 거부한다 effect 변수는 결정 위치에서 인자의 effect로 묶인다. 순서가 중요해서 한 번 틀렸다 — 포함 검사를 unify보다 먼저 하면 아직 해소되지 않은 미지수를 제약으로 오해해 정당한 코드를 거부한다. unify가 먼저고, 남는 차이만이 위반이다. 결정되지 않은 미지수는 판정을 미룬다 — 모르는 것을 위반이라고 말하지 않는다. 보안 정리 (i)을 직접 구현했다: capability 메서드는 값을 통해서만 부를 수 있다. 타입 이름으로 부를 수 있으면 capability 없이 effect를 수행하게 되어 정리가 무너진다. effect 검사는 타입 검사와 같은 순회에서 돈다. effect 변수의 해소가 타입 변수와 같은 지점에서 일어나므로 떼어내면 순회와 인스턴스화를 두 번 한다. 소유는 나뉘되 순회는 하나다 — 문서에 근거를 적었다. 10_effect_errors.cool 추가. capability를 직접 정의해야 메서드의 effect가 알려지므로 외부 타입을 하나도 쓰지 않는다. 통과해야 하는 6개와 거부해야 하는 7개가 모두 의도대로 갈린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+103
-15
@@ -1,11 +1,12 @@
|
||||
(* 타입 표현과 지역 unification.
|
||||
(* 타입과 effect 표현, 그리고 지역 unification.
|
||||
|
||||
TUnknown이 핵심이다. 외부 모듈에서 오는 이름은 모듈 로딩이 없는 v0에서
|
||||
해소할 수 없다. 그런 타입은 TUnknown이 되고 무엇과도 맞는다 — 모르는 것을
|
||||
틀렸다고 말하지 않기 위해서다. 아는 범위에서만 검사한다.
|
||||
틀렸다고 말하지 않기 위해서다.
|
||||
|
||||
TMeta는 호출 지점에서 제네릭을 인스턴스화할 때 생기는 미지수다. 함수 하나
|
||||
범위에서만 살고 전역으로 흐르지 않는다 (철학 2: 전역 추론 없음). *)
|
||||
effect는 순서 없는 집합이고 합성은 합집합이다. 변수는 집합 변수이며,
|
||||
호출 지점에서 결정 위치(파라미터의 effect 자리에 단독으로 선 변수)를 통해
|
||||
메타에 묶인다. 함수 하나 범위를 넘지 않는다. *)
|
||||
|
||||
type t =
|
||||
| TUnknown
|
||||
@@ -15,20 +16,84 @@ type t =
|
||||
| TUnit
|
||||
| TVar of string
|
||||
| TCon of string * t list
|
||||
| TFn of { affine : bool; params : t list; ret : t }
|
||||
| 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 -> "?"
|
||||
@@ -39,10 +104,11 @@ let rec show t =
|
||||
| TVar v -> v
|
||||
| TCon (n, []) -> n
|
||||
| TCon (n, args) -> n ^ "[" ^ String.concat ", " (List.map show args) ^ "]"
|
||||
| TFn { affine; params; ret } -> (
|
||||
| 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 _ } -> "?"
|
||||
@@ -54,8 +120,19 @@ let rec occurs r t =
|
||||
| TFn { params; ret; _ } -> List.exists (occurs r) params || occurs r ret
|
||||
| _ -> false
|
||||
|
||||
(* 성공하면 true. 실패해도 예외를 던지지 않는다 — 호출자가 위치를 알고
|
||||
진단을 만든다. *)
|
||||
(* 결정 위치의 해소: 파라미터의 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
|
||||
@@ -75,19 +152,30 @@ let rec unify a b =
|
||||
소유한다. 여기서 섞으면 두 검사가 서로의 결론을 앞질러 버린다. *)
|
||||
List.length f.params = List.length g.params
|
||||
&& List.for_all2 unify f.params g.params
|
||||
&& unify f.ret g.ret
|
||||
&& unify_eff f.eff g.eff && unify f.ret g.ret
|
||||
| _ -> false
|
||||
|
||||
(* 제네릭 인스턴스화: TVar를 주어진 대입으로 바꾼다 *)
|
||||
let rec subst env t =
|
||||
(* 제네릭 인스턴스화: 타입 변수와 effect 변수를 동시에 바꾼다 *)
|
||||
let rec subst tenv eenv t =
|
||||
match resolve t with
|
||||
| TVar v -> ( match List.assoc_opt v env with Some u -> u | None -> TVar v)
|
||||
| TCon (n, args) -> TCon (n, List.map (subst env) args)
|
||||
| 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 env) f.params;
|
||||
ret = subst env f.ret;
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user