D5를 고친다. 함수 타입에 own을 적을 수 없어 "소유권을 가져가는 클로저"를 표현할 수 없었고, move 검사기가 클로저 파라미터를 무조건 소유로 봐서 고차 경계에서 소유권 검사가 뚫려 있었다. 클로저 파라미터의 소유권은 리터럴이 스스로 적는다. 타입은 기대 타입에서 읽어오지만 소유권은 읽어오지 않는다 — move 검사는 타입 검사와 별도 순회라 타입을 모르고, 소유권은 타입보다 결과가 크기 때문이다. unify는 정확히 일치를 요구한다. 방향을 다루려면 부분 타입이 필요하고 없다. 그리고 구멍이 자기가 숨긴 버그를 덮고 있었다. std/list.cool의 fold가 f: fn(acc, a) -> acc 로 적혀 있었는데 틀렸다 — 누적자는 매 단계 소비되고 새것으로 바뀌므로 own이다. 빌림으로 적혀 있어 affine 값을 fold로 실어나를 수 없었는데, 클로저 파라미터를 소유로 봤으니 아무 오류도 안 났다. 고치니 samples/app이 즉시 깨졌고, own을 붙여 고쳤다. 남은 한계를 기록했다: move 검사는 타입이 없어 제네릭을 통과해 affinity를 보지 못한다. 양쪽 다 표기가 없으면 통과한다. 근본 해법은 두 순회를 합치는 것이고 v0에서는 하지 않는다. 대가도 기록했다: own이 흔해진다. fold가 항상 요구하므로 copyable 누적자에도 붙는다. 표기의 신호가 약해지는지 지켜본다. 문법 먼저 고치고 대조 장치가 파서를 지적하게 했다. 지금은 문장 500개, 파일 29개 모두 갈림 0건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
198 lines
6.9 KiB
OCaml
198 lines
6.9 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
|
|
(* params의 bool은 소유권이다 (own이면 true). affinity와 다른 축이다 —
|
|
affinity는 타입의 성질이고 소유권은 이 자리가 값을 가져가는가다. *)
|
|
| TFn of { affine : bool; params : (bool * 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 (fun (o, t) -> (if o then "own " else "") ^ show t) 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 (fun (_, t) -> occurs r t) 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
|
|
(fun (o1, t1) (o2, t2) -> o1 = o2 && unify t1 t2)
|
|
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 (fun (o, t) -> (o, subst tenv eenv t)) 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)
|